# -*- encoding: utf-8 -*-
"""
keri.db.dbing module
import lmdb
db = lmdb.open("/tmp/keri_db_setup_test")
db.max_key_size()
511
The dupsort, integerkey, integerdup, and dupfixed parameters are ignored
if the database already exists.
The state of those settings are persistent and immutable per database.
See _Database.flags() to view the state of those options for an opened database.
A consequence of the immutability of these flags is that the default non-named
database will never have these flags set.
So only need to set dupsort first time opened each other opening does not
need to call it
"""
import os
import shutil
from collections import namedtuple
from contextlib import contextmanager
from dataclasses import dataclass, asdict, field
import json
import cbor2 as cbor
import msgpack
import lmdb
from ordered_set import OrderedSet as oset
from hio.base import doing
from . import dbing, koming, subing
from .. import kering
from ..core import coring, eventing, parsing, serdering
from .. import help
from ..help import helping
logger = help.ogler.getLogger()
[docs]
class dbdict(dict):
"""
Subclass of dict that has db as attribute and employs read through cache
from db Baser.stts of kever states to reload kever from state in database
when not found in memory as dict item.
"""
__slots__ = ('db') # no .__dict__ just for db reference
def __init__(self, *pa, **kwa):
super(dbdict, self).__init__(*pa, **kwa)
self.db = None
def __getitem__(self, k):
try:
return super(dbdict, self).__getitem__(k)
except KeyError as ex:
if not self.db:
raise ex # reraise KeyError
if (ksr := self.db.states.get(keys=k)) is None:
raise ex # reraise KeyError
try:
kever = eventing.Kever(state=ksr, db=self.db)
except kering.MissingEntryError: # no kel event for keystate
raise ex # reraise KeyError
self.__setitem__(k, kever)
return kever
def __contains__(self, k):
if not super(dbdict, self).__contains__(k):
try:
self.__getitem__(k)
return True
except KeyError:
return False
else:
return True
[docs]
def get(self, k, default=None):
"""Override of dict get method
Parameters:
k (str): key for dict
default: default value to return if not found
Returns:
kever: converted from underlying dict or database
"""
if not super(dbdict, self).__contains__(k):
return default
else:
return self.__getitem__(k)
[docs]
@dataclass
class RawRecord:
"""RawRecord is base class for dataclasses that provides private utility
methods for representing the dataclass as some other format like dict,
json bytes, cbor bytes, mgpk bytes as a raw format. Typically uses case
is to transform dataclass into dict or serialization of its transformation
into dict so that it can be included in messages or stored in a database.
"""
@classmethod
def _fromdict(cls, d: dict):
"""returns instance of clas initialized from dict d """
return helping.datify(cls, d)
def __iter__(self):
return iter(asdict(self))
def _asdict(self):
"""Returns dict version of record"""
return helping.dictify(self)
def _asjson(self):
"""Returns json bytes version of record"""
return json.dumps(self._asdict(),
separators=(",", ":"),
ensure_ascii=False).encode("utf-8")
def _ascbor(self):
"""Returns cbor bytes version of record"""
return cbor.dumps(self._asdict())
def _asmgpk(self):
"""Returns mgpk bytes version of record"""
return msgpack.dumps(self._asdict())
[docs]
@dataclass
class StateEERecord(RawRecord):
"""
Corresponds to StateEstEvent namedtuple used as sub record in KeyStateRecord
for latest establishment event associated with current key state
Attributes:
s (str): sequence number of latest est evt lowercase hex no leading zeros
d (str): SAID qb64 of latest est evt
br (list[str]): backer aids qb64 remove list (cuts) from latest est event
ba (list[str]): backer aids qb64 add list (adds) from latest est event
"""
s: str ='0' # sequence number of latest event in KEL as hex str
d: str ='' # latest event digest qb64
br: list = field(default_factory=list) # backer AID qb64 remove (cut) list
ba: list = field(default_factory=list) # backer AID qb64 add list
[docs]
@dataclass
class KeyStateRecord(RawRecord): # baser.state
"""
Key State information keyed by Identifier Prefix of associated KEL.
For local AIDs that correspond to Habs this is the Hab AID.
(see baser.state at 'stts')
Attributes:
vn (list[int]): version number [major, minor]
i (str): identifier prefix qb64
s (str): sequence number of latest event in KEL as hex str
p (str): prior event digest qb64
d (str): latest event digest qb64
f (str): first seen ordinal number of latest event in KEL as hex str
dt (str): datetime iso-8601 of key state record update, usually now
et (str): latest event packet type
kt (str): signing threshold sith
k (list[str]): signing keys qb64
nt (str): next prerotated threshold sith
n (list[str]): pre-rotation keys qb64
bt (str): backer threshold hex num
b (list[str]): backer aids qb64
c (list[str]): config traits
ee (StateEERecord): instance
corresponds to StateEstEvent namedtuple
s = sn of latest est event as lowercase hex string no leading zeros,
d = SAID digest qb64 of latest establishment event
br = backer (witness) remove list (cuts) from latest est event
ba = backer (witness) add list (adds) from latest est event
di (str): delegator aid qb64 or empty str if not delegated
Note: the seal anchor dict 'a' field is not included in the state notice
because it may be verbose and would impede the main purpose of a notic which
is to trigger the download of the latest events, which would include the
anchored seals.
"""
vn: list[int] = field(default_factory=list) # version number [major, minor] round trip serializable
i: str ='' # identifier prefix qb64
s: str ='0' # sequence number of latest event in KEL as hex str
p: str ='' # prior event digest qb64
d: str ='' # latest event digest qb64
f: str ='0' # first seen ordinal number of latest event in KEL as hex str
dt: str = '' # datetime of creation of state
et: str = '' # latest evt packet type (ilk)
kt: str = '0' # signing threshold sith
k: list[str] = field(default_factory=list) # signing key list qb64
nt: str = '0' # next rotation threshold nsith
n: list[str] = field(default_factory=list) # next rotation key digest list qb64
bt: str = '0' # backer threshold hex num str
b: list = field(default_factory=list) # backer AID list qb64
c: list[str] = field(default_factory=list) # config trait list
ee: StateEERecord = field(default_factory=StateEERecord)
di: str = '' # delegator aid qb64 if any otherwise empty '' str
[docs]
@dataclass
class HabitatRecord: # baser.habs
"""
Habitat application state information keyed by habitat name (baser.habs)
Attributes:
hid (str): identifier prefix of hab qb64
mid (str | None): group member identifier qb64 when hid is group
smids (list | None): group signing member identifiers qb64 when hid is group
rmids (list | None): group signing member identifiers qb64 when hid is group
watchers: (list[str]) = list of id prefixes qb64 of watchers
"""
hid: str # hab own identifier prefix qb64
mid: str | None = None # group member identifier qb64 when hid is group
smids: list | None = None # group signing member ids when hid is group
rmids: list | None = None # group rotating member ids when hid is group
sid: str | None = None # Signify identifier qb64 when hid is Signify
watchers: list[str] = field(default_factory=list) # id prefixes qb64 of watchers
[docs]
@dataclass
class TopicsRecord: # baser.tops
"""
Tracks the last message topic index retrieved from the witness mailbox
Database Key is the identifier prefix of the witness that is storing
events in a mailbox. (baser.tops)
"""
topics: dict
[docs]
@dataclass
class OobiQueryRecord: # information for responding to OOBI query
"""
Keyed by cid in oobis field of HabitatRecord (oobiq).
Determines which endpoints are allowed as responses to oobi query for cid
cid is aid of controller with endpoint.
role is functional role of endpoint provider
eids are aids of endpoint providers for a role.
schemes are url schemes of endpoint url
This record acts as a constraint tree with path cid.role.eid.scheme.
Partial path specification permits the resultant subtree. Full path
specification permits only the leaf. No record could be either all allowed
or none allowed depending on the habitat type or function. Defaults rules
for each pairing of querier and replier.
This functionality is aspirational for now. It is likely that we need an
endpoint identity constraint graph to properly model the endpoint relationship
permissing constraint structure. For now we just operate with a promiscuous
constraint policy for endpoint discovery .
Usage:
oobiqs: dict[str, OobiQueryRecord] = field(default_factory=dict)
"""
cid: str = None # qb64
role: str = None # one of kering.Roles None is any or all
eids: list[str] = field(default_factory=list) # of qb64 empty is any
scheme: str = None # one of kering.Schemes None is any or all
def __iter__(self):
return iter(asdict(self))
[docs]
@dataclass
class OobiRecord:
"""
Keyed by CID (AID) and role, the minimum information needed for any OOBI
"""
oobialias: str = None
said: str = None
cid: str = None
eid: str = None
role: str = None
date: str = None
state: str = None
urls: list = None
[docs]
@dataclass
class EndpointRecord: # baser.ends
"""
Service Endpoint ID (SEID) Record with fields and keys to manage endpoints by
cid,role, and eid. Serves as aggregation mechanism for authorization and other
functions such as UX naming with regards the endpoint.
The namespace is a tree of branches with each leaf at a
specific (cid, role, eid). Retrieval by branch returns groups of leaves as
appropriate for a cid braanch or cid.role branch.
Database Keys are (cid, role, eid) where cid is attributable controller identifier
(qb64 prefix) that has role(s) such as watcher, witness etc and eid is the
identifier of the controller acting in a role i.e. watcher identifier.
Attributes:
allowed (bool): AuthZ via reply message
True means eid is allowed as controller of endpoint in role
False means eid is disallowed as conroller of endpint in role
None means eid is neither allowed or disallowed (no reply msg)
enabled (bool): AuthZ via expose message
True means eid is enabled as controller of endpoint in role
False means eid is disenabled as conroller of endpint in role
None means eid is neither enabled or disenabled (no expose msg)
name (str): user fieldly name for eid in role
An end authorization reply message is required from which the field values
for this record are extracted. A routes of /end/role/eid/add /end/role/eid/cut
Uses add-cut model with allowed field
allowed==True eid is allowed (add) as endpoint provider for cid at role and name
allowed==False eid is disallowed (cut) as endpoint provider for cid at role and name
{
"v" : "KERI10JSON00011c_",
"t" : "rep",
"d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM",
"dt": "2020-08-22T17:50:12.988921+00:00",
"r" : "/end/role/add",
"a" :
{
"cid": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM",
"role": "watcher", # one of kering.Roles
"eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE",
}
}
{
"v" : "KERI10JSON00011c_",
"t" : "rep",
"d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM",
"dt": "2020-08-22T17:50:12.988921+00:00",
"r" : "/end/role/cut",
"a" :
{
"cid": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM",
"role": "watcher", # one of kering.Roles
"eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE",
}
}
An end authorization expose message provides enablement via an exposure of
and anchored seal to the expose message on the authorizing KEL.
"""
allowed: bool = None # True eid allowed (add), False eid disallowed (cut), None neither
enabled: bool = None # True eid enabled (add), False eid disenabled (cut), None neither
name: str = "" # optional user friendly name of endpoint
def __iter__(self):
return iter(asdict(self))
[docs]
@dataclass
class EndAuthRecord: # nested as field value in baser.locs
"""
Service Endpoint Authorization Record provides cross reference field for search
purposes to find authorization for endpoint provider eid. The default is
controller id, cid, and a role. used to lookup authorization in end authN
database with keyspace given by (cid.role.eid) where cid is the authorizing
controller for the eid (endpoint id) at the given role.
The cid is usually a transferable identifier with a KEL but may be non-trans.
The eid is usually a nontransferable identifier when its used for roles
witness or watcher but may be transferable for other roles such as controller,
judge, juror, public watcher, or registrar.
This is an embedded record type in a LocationRecord in the cids field
"""
cid: str = "" # identifier prefix of controller that authorizes endpoint
roles: list[str] = field(default_factory=list) # str endpoint roles such as watcher, witness etc
def __iter__(self):
return iter(asdict(self))
[docs]
@dataclass
class LocationRecord: # baser.locs
"""
Service Endpoint Record with url for endpoint of a given scheme The eid is
usually a nontransferable identifier when its used for roles witness or watcher
but may be transferable for other roles such as controller, judge, juror,
public watcher, or registrar.
Database Keys are (eid, scheme) where eid is service endpoint identifier
(qb64 prefix) and scheme is the url protocol scheme (tcp, https).
A loc reply message is required from which the values of this
database record are extracted. route is /loc/scheme Uses enact-anul model
To nullify endpoint set url field to empty.
An end authorization reply message is also required to authorize the eid as
endpoint provider for cid at role. See EndpointRecord
{
"v" : "KERI10JSON00011c_",
"t" : "rep",
"d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM",
"dt": "2020-08-22T17:50:12.988921+00:00",
"r" : "/loc/scheme",
"a" :
{
"eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE",
"scheme": "http", # one of kering.Schemes
"url": "http://localhost:8080/watcher/wilma",
}
}
{
"v" : "KERI10JSON00011c_",
"t" : "rep",
"d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM",
"dt": "2020-08-22T17:50:12.988921+00:00",
"r" : "/loc/scheme",
"a" :
{
"eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE",
"scheme": "http", # one of kering.Schemes
"url": "",
}
}
"""
url: str # full url including host:port/path?query scheme is optional
# cids: list[EndAuthRecord] = field(default_factory=list) # optional authorization record references
def __iter__(self):
return iter(asdict(self))
[docs]
@dataclass
class WellKnownAuthN:
"""
Each WellKnownAuthN represents a successfully resolved .well-known OOBI URL keyed by
the AID of the OOBI tuple embedded in the URL
"""
url: str # full .well-known OOBI URL resolved
dt: str # iso8601 date/time of success resolution
[docs]
def openDB(*, cls=None, name="test", **kwa):
"""
Returns contextmanager generated by openLMDB but with Baser instance as default
"""
if cls == None: # can't reference class before its defined below
cls = Baser
return dbing.openLMDB(cls=cls, name=name, **kwa)
[docs]
@contextmanager
def reopenDB(db, clear=False, **kwa):
"""
Context manager wrapper LMDB DB instances.
Repens and closes db.path and db.env LMDB
Parameters:
db (LMDBer): instance with LMDB environment at .env
clear (bool): True means clear directory after close
Usage:
with reopenDB(baser) as env:
env. ....
"""
try:
db.reopen(clear=clear, **kwa)
yield db.env
finally:
db.close(clear=clear)
[docs]
class Baser(dbing.LMDBer):
"""
Baser sets up named sub databases with Keri Event Logs within main database
Attributes:
see superclass LMDBer for inherited attributes
kevers (dict): Kever instances indexed by identifier prefix qb64
prefixes (OrderedSet): local prefixes corresponding to habitats for this db
.evts is named sub DB whose values are serialized events
dgKey
DB is keyed by identifier prefix plus digest of serialized event
Only one value per DB key is allowed
.fels is named sub DB of first seen event log table (FEL) of digests
that indexes events in first 'seen' accepted order for replay and
cloning of event log. Only one value per DB key is allowed.
Provides append only ordering of accepted first seen events.
Uses first seen order number or fn.
fnKey
DB is keyed by identifier prefix plus monotonically increasing first
seen order number fn.
Value is digest of serialized event used to lookup event in .evts sub DB
.dtss is named sub DB of datetime stamp strings in ISO 8601 format of
the datetime when the event was first escrosed and then later first
seen by log. Used for escrows timeouts and extended validation.
dgKey
DB is keyed by identifier prefix plus digest of serialized event
Value is ISO 8601 datetime stamp bytes
.aess is named sub DB of authorizing event source seal couples
that map digest to seal source couple of authorizer's
(delegator or issuer) event. Each couple is a concatenation of full
qualified items, snu+dig of the authorizing (delegating or issuing)
source event.
dgKey
Values are couples used to lookup authorizer's source event in
.kels sub DB
DB is keyed by identifier prefix plus digest of key event
Only one value per DB key is allowed
.sigs is named sub DB of fully qualified indexed event signatures
dgKey
DB is keyed by identifier prefix plus digest of serialized event
More than one value per DB key is allowed
.wigs is named sub DB of indexed witness signatures of event
Witnesses always have nontransferable indetifier prefixes.
The index is the offset of the witness into the witness list
of the most recent establishment event wrt the receipted event.
dgKey
DB is keyed by identifier prefix plus digest of serialized event
More than one value per DB key is allowed
.rcts is named sub DB of event receipt couplets from nontransferable
signers. Each couple is concatenation of fully qualified items.
These are: non-transferale prefix plus non-indexed event signature
by that prefix.
dgKey
DB is keyed by identifier prefix plus digest of serialized event
More than one value per DB key is allowed
.ures is named sub DB of unverified event receipt escrowed triples from
non-transferable signers. Each triple is concatenation of fully
qualified items. These are: receipted event digest,
non-transferable receiptor identifier prefix,
plus nonindexed receipt event signature by that prefix.
snKey
DB is keyed by receipted event controller prefix plus sn
of serialized event
More than one value per DB key is allowed
.vrcs is named sub DB of event validator receipt quadruples from transferable
signers. Each quadruple is concatenation of four fully qualified items
of validator. These are: transferable prefix, plus latest establishment
event sequence number plus latest establishment event digest,
plus indexed event signature.
When latest establishment event is multisig then there will
be multiple quadruples one per signing key, each a dup at same db key.
dgKey
DB is keyed by identifier prefix plus digest of serialized event
More than one value per DB key is allowed
.vres is named sub DB of unverified event validator receipt escrowed
quadruples from transferable signers. Each quadruple is concatenation of
four fully qualified items of validator. These are: transferable prefix,
plus latest establishment event sequence number plus latest
establishment event digest, plus indexed event signature.
When latest establishment event is multisig then there will
be multiple quadruples one per signing key, each a dup at same db key.
dgKey
DB is keyed by identifier prefix plus digest of serialized event
More than one value per DB key is allowed
.kels is named sub DB of key event log tables that map sequence numbers
to serialized event digests.
snKey
Values are digests used to lookup event in .evts sub DB
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.pses is named sub DB of partially signed escrowed event tables
that map sequence numbers to serialized event digests.
snKey
Values are digests used to lookup event in .evts sub DB
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.pdes is named sub DB of partially delegated escrowed couples
that map digest to seal source couple that provides source
(delegator or issuer) event seal. Each couples is concatenations
of full qualified items, snu+dig of authorizing (delegating or
issuing) source event.
dgKey
Values are couples used to lookup source event in .kels sub DB
DB is keyed by identifier prefix plus digest of key event
Only one value per DB key is allowed
.pwes is named sub DB of partially witnessed escrowed event tables
that map sequence numbers to serialized event digests.
snKey
Values are digests used to lookup event in .evts sub DB
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.uwes is named sub DB of unverified event indexed escrowed couples from
witness signers. Witnesses are from witness list of latest establishment
event for the receipted event. Each couple is concatenation of fully
qualified items, edig+sig where:
edig is receipted event digest
wig is indexed signature of that event with keypair derived from
witness nontrans identifier prefix from witness list and index
is offset into witness list of latest establishment event for
receipted event
snKey
DB is keyed by receipted event controller prefix plus sn
of serialized event
More than one value per DB key is allowed
.ooes is named sub DB of out of order escrowed event tables
that map a prefix and sequence number to a set of serialized event
digests.
Values are digests used to lookup event in .evts, .sigs and .dtss
sub DBs.
snKey
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.dels is named sub DB of duplicitous event log tables that map sequence numbers
to serialized event digests.
snKey
Values are digests used to lookup event in .evts sub DB
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.ldes is named sub DB of likely duplicitous escrowed event tables
that map sequence numbers to serialized event digests.
snKey
Values are digests used to lookup event in .evts sub DB
DB is keyed by identifier prefix plus sequence number of key event
More than one value per DB key is allowed
.fons is named subDB instance of MatterSuber that maps
(prefix, digest) e.g. dgKey to fn value (first seen ordinal number) of
the associated event. So one can lookup event digest, get its fn here
and then use fn to fetch event by fn from .fels.
.states (stts) is named subDB instance of SerderSuber that maps a prefix
to the latest keystate for that prefix. Used by ._kevers.db for read
through cache of key state to reload kevers in memory
.habs is named subDB instance of Komer that maps habitat names to habitat
application state. Includes habitat identifier prefix
key is habitat name str
value is serialized HabitatRecord dataclass
.nmsp is named subDB instance of Komer that maps habitat namespaces and names to habitat
application state. Includes habitat identifier prefix
key is habitat namespace + b'\x00' + name str
value is serialized HabitatRecord dataclass
.sdts (sad date-time-stamp) named subDB instance of CesrSuber that
that maps SAD SAID to Dater instance's CESR serialization of
ISO-8601 datetime
key = said (bytes) of sad, val = dater.qb64b
.ssgs (sad trans indexed sigs) named subDB instance of CesrIoSetSuber
that maps keys quadruple (saider.qb64, prefixer.qb64, seqner.q64,
diger.qb64) to val Siger of trans id siganture. Where: saider is
said of SAD and prefixer, seqner, and diger indicate the key state
est event for signer or reply SAD. Each key may
have a set of vals in insertion order one for each signer of the sad.
key = join (saider.qb64b, prefixer.qb64b, seqner.qb64b, diger.qb64b)
(bytes) val = siger.qb64b
.scgs (sad nontrans cigs) named subDB instance of CatCesrIoSetSuber
that maps said of SAD to couple (Verfer, Cigar) for nontrans signer.
For nontrans qb64 of Verfer is same as Prefixer.
Each key may have a set of vals in insertion order one for each
nontrans signer of the sad.
key = said (bytes of SAD, val = cat of (verfer.qb64, cigar.qb64b)
.rpys (replys) named subDB instance of SerderSuber that maps said of
reply message (versioned SAD) to serialization of that reply message.
key is said bytes, val is Serder.raw bytes of reply 'rpy' message
.rpes (reply escrows) named subDB instance of CesrIoSetSuber that
maps routes of reply (versioned SAD) to single Saider of that
reply msg.
Routes such as '/end/role/' and '/loc/scheme'
key is route bytes, vals = saider.qb64b of reply 'rpy' msg
.eans is named subDB instance of CesrSuber with klas=Saider that maps
cid.role.eid to said of reply SAD as auth: authN by controller cid
of authZ that designates endpoint provider eid in role
routes /end/role/add and /end/role/cut to nullify
key is cid.role.eid, val = saider.qb64b of reply 'rpy' msg SAD
.lans is named subDB instance of CesrSuber with klas=Saider that maps
eid.scheme to said of reply SAD as auth: authN by endpoint provider
eid that designates scheme for url
route /loc/scheme use null url to nullify
key is eid.scheme, val = saider.qb64b of reply 'rpy' msg SAD
.ends is named subDB instance of Komer that maps (cid, role, eid)
to attributes about endpoint authorization where:
cid is controller prefix, role is endpoint role, watcher etc, and
eid is controller prefix of endpoint controller watcher etc.
key is cid.role.eid, value is serialized EndpointRecord dataclass
.locs is named subDB instance of Komer that maps endpoint prefix eid
and endpoint network location scheme to endpoint location details
key is eid.scheme, val is serialized LocationRecord dataclass
.tops is named subDB instance of Komer that maps Witness identifier
prefix to topic index of last received mailbox message.
key is witness prefix identifier
value is serialized TopicsRecord dataclass
.gids is named subDB instance of Komer that maps group identifier prefix
to the local identifier prefix and list of remote identifier prefixes
that participate in the group identifier.
key is group identifier prefix
value is serialized GroupIdentifier dataclass
.mpids is named subDB instance of CesrIoSetSuber mapping payload SAID (of 'e' block)
to the SAID of the `exn` messages is was contained in. This aggregates
identical message bodies across participants in group multisig body trying
to reach concensus on events or credentials.
Properties:
kevers (dbdict): read through cache of kevers of states for KELs in db
"""
[docs]
def __init__(self, headDirPath=None, reopen=False, **kwa):
"""
Setup named sub databases.
Parameters:
name is str directory path name differentiator for main database
When system employs more than one keri database, name allows
differentiating each instance by name
temp is boolean, assign to .temp
True then open in temporary directory, clear on close
Othewise then open persistent directory, do not clear on close
headDirPath is optional str head directory pathname for main database
If not provided use default .HeadDirpath
mode is int numeric os dir permissions for database directory
reopen (bool): True means database will be reopened by this init
"""
self.prefixes = oset()
self._kevers = dbdict()
self._kevers.db = self # assign db for read through cache of kevers
super(Baser, self).__init__(headDirPath=headDirPath, reopen=reopen, **kwa)
@property
def kevers(self):
"""
Returns .db.kevers
"""
return self._kevers
[docs]
def reopen(self, **kwa):
"""
Open sub databases
Notes:
dupsort=True for sub DB means allow unique (key,pair) duplicates at a key.
Duplicate means that is more than one value at a key but not a redundant
copies a (key,value) pair per key. In other words the pair (key,value)
must be unique both key and value in combination.
Attempting to put the same (key,value) pair a second time does
not add another copy.
Duplicates are inserted in lexocographic order by value, insertion order.
"""
super(Baser, self).reopen(**kwa)
# Create by opening first time named sub DBs within main DB instance
# Names end with "." as sub DB name must include a non Base64 character
# to avoid namespace collisions with Base64 identifier prefixes.
self.evts = self.env.open_db(key=b'evts.')
self.fels = self.env.open_db(key=b'fels.')
self.dtss = self.env.open_db(key=b'dtss.')
self.aess = self.env.open_db(key=b'aess.')
self.sigs = self.env.open_db(key=b'sigs.', dupsort=True)
self.wigs = self.env.open_db(key=b'wigs.', dupsort=True)
self.rcts = self.env.open_db(key=b'rcts.', dupsort=True)
self.ures = self.env.open_db(key=b'ures.', dupsort=True)
self.vrcs = self.env.open_db(key=b'vrcs.', dupsort=True)
self.vres = self.env.open_db(key=b'vres.', dupsort=True)
self.kels = self.env.open_db(key=b'kels.', dupsort=True)
self.pses = self.env.open_db(key=b'pses.', dupsort=True)
self.pdes = self.env.open_db(key=b'pdes.')
self.pwes = self.env.open_db(key=b'pwes.', dupsort=True)
self.uwes = self.env.open_db(key=b'uwes.', dupsort=True)
self.ooes = self.env.open_db(key=b'ooes.', dupsort=True)
self.dels = self.env.open_db(key=b'dels.', dupsort=True)
self.ldes = self.env.open_db(key=b'ldes.', dupsort=True)
self.qnfs = self.env.open_db(key=b'qnfs.', dupsort=True)
# events as ordered by first seen ordinals
self.fons = subing.CesrSuber(db=self, subkey='fons.', klas=coring.Seqner)
# Kever state made of KeyStateRecord key states
self.states = koming.Komer(db=self,
schema=KeyStateRecord,
subkey='stts.')
self.wits = subing.CesrIoSetSuber(db=self, subkey="wits.", klas=coring.Prefixer)
# habitat application state keyed by habitat name, includes prefix
self.habs = koming.Komer(db=self,
subkey='habs.',
schema=HabitatRecord, )
# habitat application state keyed by habitat namespace + b'\x00' + name, includes prefix
self.nmsp = koming.Komer(db=self,
subkey='nmsp.',
schema=HabitatRecord, )
# SAD support datetime stamps and signatures indexed and not-indexed
# all sad sdts (sad datetime serializations) maps said to date-time
self.sdts = subing.CesrSuber(db=self, subkey='sdts.', klas=coring.Dater)
# all sad ssgs (sad indexed signature serializations) maps SAD quadkeys
# given by quadruple (saider.qb64, prefixer.qb64, seqner.q64, diger.qb64)
# of reply and trans signer's key state est evt to val Siger for each
# signature.
self.ssgs = subing.CesrIoSetSuber(db=self, subkey='ssgs.', klas=coring.Siger)
# all sad scgs (sad non-indexed signature serializations) maps SAD SAID
# to couple (Verfer, Cigar) of nontrans signer of signature in Cigar
# nontrans qb64 of Prefixer is same as Verfer
self.scgs = subing.CatCesrIoSetSuber(db=self, subkey='scgs.',
klas=(coring.Verfer, coring.Cigar))
# all reply messages. Maps reply said to serialization. Replys are
# versioned sads ( with version string) so use Serder to deserialize and
# use .sdts, .ssgs, and .scgs for datetimes and signatures
self.rpys = subing.SerderSuber(db=self, subkey='rpys.')
# all reply escrows indices of partially signed reply messages. Maps
# route in reply to single (Saider,) of escrowed reply.
# Routes such as /end/role /loc/schema
self.rpes = subing.CesrIoSetSuber(db=self, subkey='rpes.',
klas=coring.Saider)
# auth AuthN/AuthZ by controller at cid of endpoint provider at eid
# maps key=cid.role.eid to val=said of end reply
self.eans = subing.CesrSuber(db=self, subkey='eans.', klas=coring.Saider)
# auth AuthN/AuthZ by endpoint provider at eid of location at scheme url
# maps key=cid.role.eid to val=said of end reply
self.lans = subing.CesrSuber(db=self, subkey='lans.', klas=coring.Saider)
# service endpoint identifier (eid) auths keyed by controller cid.role.eid
# data extracted from reply /end/role/add or /end/role/cut
self.ends = koming.Komer(db=self, subkey='ends.',
schema=EndpointRecord, )
# service endpoint locations keyed by eid.scheme (endpoint identifier)
# data extracted from reply loc
self.locs = koming.Komer(db=self,
subkey='locs.',
schema=LocationRecord, )
# index of last retrieved message from witness mailbox
self.tops = koming.Komer(db=self,
subkey='witm.',
schema=TopicsRecord, )
# group partial signature escrow
self.gpse = subing.CatCesrIoSetSuber(db=self, subkey='gpse.',
klas=(coring.Seqner, coring.Saider))
# group delegate escrow
self.gdee = subing.CatCesrIoSetSuber(db=self, subkey='gdee.',
klas=(coring.Seqner, coring.Saider))
# group partial witness escrow
self.gpwe = subing.CatCesrIoSetSuber(db=self, subkey='gdwe.',
klas=(coring.Seqner, coring.Saider))
# completed group multisig
self.cgms = subing.CesrSuber(db=self, subkey='cgms.',
klas=coring.Saider)
# exchange message partial signature escrow
self.epse = subing.SerderSuber(db=self, subkey="epse.")
# exchange messages
self.exns = subing.SerderSuber(db=self, subkey="exns.")
# Forward pointer to a provided reply message
self.erpy = subing.CesrSuber(db=self, subkey="erpy.", klas=coring.Saider)
# exchange messages
self.sxns = subing.SerderSuber(db=self, subkey="sxns.")
# exchange message signatures
self.esigs = subing.CesrIoSetSuber(db=self, subkey='esigs.', klas=coring.Siger)
# exchange message signatures
self.ecigs = subing.CatCesrIoSetSuber(db=self, subkey='ecigs.',
klas=(coring.Verfer, coring.Cigar))
# exchange pathed attachments
self.epath = subing.IoSetSuber(db=self, subkey=".epath")
# accepted signed 12-word challenge response exn messages keys by prefix of signer
self.chas = subing.CesrIoSetSuber(db=self, subkey='chas.', klas=coring.Saider)
# successfull signed 12-word challenge response exn messages keys by prefix of signer
self.reps = subing.CesrIoSetSuber(db=self, subkey='reps.', klas=coring.Saider)
# authorzied well known OOBIs
self.wkas = koming.IoSetKomer(db=self, subkey='wkas.', schema=WellKnownAuthN)
# KSN support datetime stamps and signatures indexed and not-indexed
# all ksn kdts (key state datetime serializations) maps said to date-time
self.kdts = subing.CesrSuber(db=self, subkey='kdts.', klas=coring.Dater)
# all key state messages. Maps key state said to serialization. ksns are
# KeyStateRecords so use ._asdict or ._asjson as appropriate
# use .kdts, .ksgs, and .kcgs for datetimes and signatures
self.ksns = koming.Komer(db=self,
schema=KeyStateRecord,
subkey='ksns.')
#self.ksns = subing.SerderSuber(db=self, subkey='ksns.')
# key state SAID database for successfully saved key state notices
# maps key=(prefix, aid) to val=said of key state
self.knas = subing.CesrSuber(db=self, subkey='knas.', klas=coring.Saider)
# config loaded oobis to be processed asynchronously, keyed by oobi URL
self.oobis = koming.Komer(db=self,
subkey='oobis.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# escrow OOBIs that failed to load, retriable, keyed by oobi URL
self.eoobi = koming.Komer(db=self,
subkey='eoobi.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# OOBIs with outstand client requests.
self.coobi = koming.Komer(db=self,
subkey='coobi.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# Resolved OOBIs (those that have been processed successfully for this database.
self.roobi = koming.Komer(db=self,
subkey='roobi.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# Well known OOBIs that are to be used for mfa against a resolved OOBI.
self.woobi = koming.Komer(db=self,
subkey='woobi.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# Well known OOBIs that are to be used for mfa against a resolved OOBI.
self.moobi = koming.Komer(db=self,
subkey='moobi.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# Multifactor well known OOBI auth records to process. Keys by controller URL
self.mfa = koming.Komer(db=self,
subkey='mfa.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# Resolved multifactor well known OOBI auth records. Keys by controller URL
self.rmfa = koming.Komer(db=self,
subkey='mfa.',
schema=OobiRecord,
sep=">") # Use seperator not a allowed in URLs so no splitting occurs.
# JSON schema SADs keys by the SAID
self.schema = subing.SchemerSuber(db=self,
subkey='schema.')
# Field values for contact information for remote identifiers. Keyed by prefix/field
self.cfld = subing.Suber(db=self,
subkey="cfld.")
# Global settings for the Habery environment
self.hbys = subing.Suber(db=self, subkey='hbys.')
# Signed contact data, keys by prefix
self.cons = subing.Suber(db=self,
subkey="cons.")
# Transferable signatures on contact data
self.ccigs = subing.CesrSuber(db=self, subkey='ccigs.', klas=coring.Cigar)
# Chunked image data for contact information for remote identifiers
self.imgs = self.env.open_db(key=b'imgs.')
# Delegation escrow dbs #
# delegated partial witness escrow
self.dpwe = subing.SerderSuber(db=self, subkey='dpwe.')
# delegated unanchored escrow
self.dune = subing.SerderSuber(db=self, subkey='dune.')
# completed group multisig
self.cdel = subing.CesrSuber(db=self, subkey='cdel.',
klas=coring.Saider)
# public keys mapped to the AID and event seq no they appeared in
self.pubs = subing.CatCesrIoSetSuber(db=self, subkey="pubs.",
klas=(coring.Prefixer, coring.Seqner))
# next key digests mapped to the AID and event seq no they appeared in
self.digs = subing.CatCesrIoSetSuber(db=self, subkey="digs.",
klas=(coring.Prefixer, coring.Seqner))
# multisig sig embed payload SAID mapped to containing exn messages across group multisig participants
self.meids = subing.CesrIoSetSuber(db=self, subkey="meids.", klas=coring.Saider)
# multisig sig embed payload SAID mapped to group multisig participants AIDs
self.maids = subing.CesrIoSetSuber(db=self, subkey="maids.", klas=coring.Prefixer)
self.reload()
return self.env
[docs]
def reload(self):
"""
Reload stored prefixes and Kevers from .habs
"""
removes = []
for keys, data in self.habs.getItemIter():
if (ksr := self.states.get(keys=data.hid)) is not None:
try:
kever = eventing.Kever(state=ksr,
db=self,
prefixes=self.prefixes,
local=True)
except kering.MissingEntryError as ex: # no kel event for keystate
removes.append(keys) # remove from .habs
continue
self.kevers[kever.prefixer.qb64] = kever
self.prefixes.add(kever.prefixer.qb64)
elif data.mid is None: # in .habs but no corresponding key state and not a group so remove
removes.append(keys) # no key state or KEL event for .hab record
for keys in removes: # remove bare .habs records
self.habs.rem(keys=keys)
# Load namespaced Habs
removes = []
for keys, data in self.nmsp.getItemIter():
if (ksr := self.states.get(keys=data.hid)) is not None:
try:
kever = eventing.Kever(state=ksr,
db=self,
prefixes=self.prefixes,
local=True)
except kering.MissingEntryError as ex: # no kel event for keystate
removes.append(keys) # remove from .habs
continue
self.kevers[kever.prefixer.qb64] = kever
self.prefixes.add(kever.prefixer.qb64)
elif data.mid is None: # in .habs but no corresponding key state and not a group so remove
removes.append(keys) # no key state or KEL event for .hab record
for keys in removes: # remove bare .habs records
self.nmsp.rem(keys=keys)
[docs]
def clean(self):
"""
Clean database by creating re-verified cleaned cloned copy
and then replacing original with cleaned cloned copy
Database usage should be offline during cleaning as it will be cloned in
readonly mode
"""
# create copy to clone into
with openDB(name=self.name,
temp=self.temp,
headDirPath=self.headDirPath,
perm=self.perm,
clean=True) as copy:
with reopenDB(db=self, reuse=True, readonly=True): # reopen as readonly
if not os.path.exists(self.path):
raise ValueError("Error while cleaning, no orig at {}."
"".format(self.path))
kvy = eventing.Kevery(db=copy) # promiscuous mode
# Revise in future to NOT parse msgs but to extract the processed
# objects so can pass directly to kvy.processEvent()
# need new method cloneObjAllPreIter()
# process event doesn't capture exceptions so we can more easily
# detect in the cloning that some events did not make it through
psr = parsing.Parser(kvy=kvy)
for msg in self.cloneAllPreIter(): # clone into copy
psr.parseOne(ims=msg)
# clone .habs habitat name prefix Komer subdb
# copy.habs = koming.Komer(db=copy, schema=HabitatRecord, subkey='habs.') # copy
for keys, val in self.habs.getItemIter():
if val.hid in copy.kevers: # only copy habs that verified
copy.habs.put(keys=keys, val=val)
copy.prefixes.add(val.hid)
if not copy.habs.get(keys=(self.name,)):
raise ValueError("Error cloning habs, missing orig name={}."
"".format(self.name))
# clone .ends and .locs databases
for keys, val in self.ends.getItemIter():
exists = False # only copy if entries in both .ends and .locs
for scheme in ("https", "http", "tcp"): # all supported schemes
lval = self.locs.get(keys=(val.eid, scheme))
if lval and lval.cid == keys[0] and lval.role == keys[1]:
exists = True # loc with matching cid and rol
copy.locs.put(keys=(val.eid, scheme), val=lval)
if exists: # only copy end if has at least one matching loc
copy.ends.put(keys=keys, vals=[val])
# remove own db directory replace with clean clone copy
if os.path.exists(self.path):
shutil.rmtree(self.path)
dst = shutil.move(copy.path, self.path) # move copy back to orig
if not dst: # move failed leave new in place so can manually fix
raise ValueError("Error cloning, unable to move {} to {}."
"".format(copy.path, self.path))
# replace own kevers with copy kevers by clear and copy
# future do this by loading kever from .stts key state subdb
self.kevers.clear()
for pre, kever in copy.kevers.items():
self.kevers[pre] = kever
# replace prefixes with cloned copy prefixes
# clear and clone .prefixes
self.prefixes.clear()
self.prefixes.update(copy.prefixes)
with reopenDB(db=self, reuse=True): # make sure can reopen
if not isinstance(self.env, lmdb.Environment):
raise ValueError("Error cloning, unable to reopen."
"".format(self.path))
# clone success so remove if still there
if os.path.exists(copy.path):
shutil.rmtree(copy.path)
[docs]
def clonePreIter(self, pre, fn=0):
"""
Returns iterator of first seen event messages with attachments for the
identifier prefix pre starting at first seen order number, fn.
Essentially a replay in first seen order with attachments
"""
if hasattr(pre, 'encode'):
pre = pre.encode("utf-8")
for fn, dig in self.getFelItemPreIter(pre, fn=fn):
try:
msg = self.cloneEvtMsg(pre=pre, fn=fn, dig=dig)
except Exception:
continue # skip this event
yield msg
[docs]
def cloneAllPreIter(self, key=b''):
"""
Returns iterator of first seen event messages with attachments for all
identifier prefixes starting at key. If key == b'' then rstart at first
key in databse. Use key to resume replay.
Essentially a replay in first seen order with attachments of entire
set of FELs.
Parameters:
key (bytes): fnKey(pre, fn)
"""
for pre, fn, dig in self.getFelItemAllPreIter(key=key):
try:
msg = self.cloneEvtMsg(pre=pre, fn=fn, dig=dig)
except Exception:
continue # skip this event
yield msg
[docs]
def cloneEvtMsg(self, pre, fn, dig):
"""
Clones Event as Serialized CESR Message with Body and attached Foot
Parameters:
pre (bytes): identifier prefix of event
fn (int): first seen number (ordinal) of event
dig (bytes): digest of event
Returns:
bytearray: message body with attachments
"""
msg = bytearray() # message
atc = bytearray() # attachments
dgkey = dbing.dgKey(pre, dig) # get message
if not (raw := self.getEvt(key=dgkey)):
raise kering.MissingEntryError("Missing event for dig={}.".format(dig))
msg.extend(raw)
# add indexed signatures to attachments
if not (sigs := self.getSigs(key=dgkey)):
raise kering.MissingEntryError("Missing sigs for dig={}.".format(dig))
atc.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs,
count=len(sigs)).qb64b)
for sig in sigs:
atc.extend(sig)
# add indexed witness signatures to attachments
if wigs := self.getWigs(key=dgkey):
atc.extend(coring.Counter(code=coring.CtrDex.WitnessIdxSigs,
count=len(wigs)).qb64b)
for wig in wigs:
atc.extend(wig)
# add authorizer (delegator/issuer) source seal event couple to attachments
couple = self.getAes(dgkey)
if couple is not None:
atc.extend(coring.Counter(code=coring.CtrDex.SealSourceCouples,
count=1).qb64b)
atc.extend(couple)
elif self.kevers[pre].delegated:
if serdering.SerderKERI(raw=bytes(raw)).estive:
raise kering.MissingEntryError("Missing delegator anchor seal for dig={}.".format(dig))
# add trans receipts quadruples to attachments
if quads := self.getVrcs(key=dgkey):
atc.extend(coring.Counter(code=coring.CtrDex.TransReceiptQuadruples,
count=len(quads)).qb64b)
for quad in quads:
atc.extend(quad)
# add nontrans receipts couples to attachments
if coups := self.getRcts(key=dgkey):
atc.extend(coring.Counter(code=coring.CtrDex.NonTransReceiptCouples,
count=len(coups)).qb64b)
for coup in coups:
atc.extend(coup)
# add first seen replay couple to attachments
if not (dts := self.getDts(key=dgkey)):
raise kering.MissingEntryError("Missing datetime for dig={}.".format(dig))
atc.extend(coring.Counter(code=coring.CtrDex.FirstSeenReplayCouples,
count=1).qb64b)
atc.extend(coring.Seqner(sn=fn).qb64b)
atc.extend(coring.Dater(dts=bytes(dts)).qb64b)
# prepend pipelining counter to attachments
if len(atc) % 4:
raise ValueError("Invalid attachments size={}, nonintegral"
" quadlets.".format(len(atc)))
pcnt = coring.Counter(code=coring.CtrDex.AttachedMaterialQuadlets,
count=(len(atc) // 4)).qb64b
msg.extend(pcnt)
msg.extend(atc)
return msg
[docs]
def cloneDelegation(self, kever):
"""
Recursively clone delegation chain from AID of Kever if one exits.
Parameters:
kever (Kever): Kever from which to clone the delegator's AID.
"""
if kever.delegated:
dkever = self.kevers[kever.delegator]
yield from self.cloneDelegation(dkever)
for dmsg in self.clonePreIter(pre=kever.delegator, fn=0):
yield dmsg
[docs]
def findAnchoringSealEvent(self, pre, seal, sn=0):
"""
Search through a KEL for the event that contains a specific anchored
SealEvent type of provided seal but in dict form and is also fully
witnessed. Searchs from sn forward (default = 0).Searches all events in
KEL of pre including disputed and/or superseded events.
Returns the Serder of the first event with the anchored SealEvent seal,
None if not found
Parameters:
pre (bytes|str): identifier of the KEL to search
seal (dict): dict form of Seal of any type SealEvent to find in anchored
seals list of each event
sn (int): beginning sn to search
"""
if tuple(seal.keys()) != eventing.SealEvent._fields: # wrong type of seal
return None
seal = eventing.SealEvent(**seal) #convert to namedtuple
for evt in self.getEvtPreIter(pre=pre, sn=sn): # includes disputed & superseded
srdr = serdering.SerderKERI(raw=evt.tobytes())
for eseal in srdr.seals or []:
if tuple(eseal.keys()) == eventing.SealEvent._fields:
eseal = eventing.SealEvent(**eseal) # convert to namedtuple
if seal == eseal and self.fullyWitnessed(srdr):
return srdr
return None
[docs]
def findAnchoringSeal(self, pre, seal, sn=0):
"""
Search through a KEL for the event that contains an anchored
Seal with same Seal type as provided seal but in dict form.
Searchs from sn forward (default = 0). Only searches last event at any
sn therefore does not search any disputed or superseded events.
Returns the Serder of the first event with the anchored Seal seal,
None if not found
Parameters:
pre (bytes|str): identifier of the KEL to search
seal (dict): dict form of Seal of any type to find in anchored
seals list of each event
sn (int): beginning sn to search
"""
# create generic Seal namedtuple class using keys from provided seal dict
Seal = namedtuple('Seal', seal.keys()) # matching type
for evt in self.getEvtLastPreIter(pre=pre, sn=sn): # only last evt at sn
srdr = serdering.SerderKERI(raw=evt.tobytes())
for eseal in srdr.seals or []:
if tuple(eseal.keys()) == Seal._fields: # same type of seal
eseal = Seal(**eseal) #convert to namedtuple
if seal == eseal and self.fullyWitnessed(srdr):
return srdr
return None
[docs]
def findAnchoringSealEventClone(self, pre, seal):
"""
Search through a KEL for the event that contains a specific anchored
SealEvent type of provided seal but in dict form.
Returns the Serder of the first event with the anchored SealEvent seal,
None if not found
Searchs from inception forward
Parameters:
pre is qb64 identifier of the KEL to search
seal is dict form of SealEvent to find in anchored seals list of each event
"""
if tuple(seal.keys()) != eventing.SealEvent._fields: # wrong type of seal
return None
#raise ValueError(f"Expected SealEvent got {seal}.")
seal = eventing.SealEvent(**seal) #convert to namedtuple
# getEvtPreIter getEvtLastPreIter
for evt in self.clonePreIter(pre=pre): # all events including superseded
srdr = serdering.SerderKERI(raw=evt)
for eseal in srdr.seals or []:
if tuple(eseal.keys()) == eventing.SealEvent._fields:
eseal = eventing.SealEvent(**eseal) #convert to namedtuple
if seal == eseal and self.fullyWitnessed(srdr):
return srdr
#spre = anc["i"]
#ssn = int(anc["s"], 16)
#sdig = anc["d"]
#if spre == seal["i"] and ssn == int(seal["s"], 16) \
#and seal["d"] == sdig and self.fullyWitnessed(srdr):
#return srdr
return None
[docs]
def findAnchoringSealClone(self, pre, seal):
"""
Search through a KEL for the event that contains an anchored
Seal with same Seal type as provided seal but in dict form.
Returns the Serder of the first event with the anchored Seal seal,
None if not found
Searchs from inception forward
Parameters:
pre is qb64 identifier of the KEL to search
seal is dict form of Seal of any type to find in anchored seals list of each event
"""
# create generic Seal namedtuple class using keys from provided seal dict
Seal = namedtuple('Seal', seal.keys()) # matching type
# getEvtPreIter getEvtLastPreIter
for evt in self.clonePreIter(pre=pre): # all events including superseded
srdr = serdering.SerderKERI(raw=evt)
for eseal in srdr.seals or []:
if tuple(eseal.keys()) == Seal._fields: # same type of seal
eseal = Seal(**eseal) #convert to namedtuple
if seal == eseal and self.fullyWitnessed(srdr):
return srdr
return None
[docs]
def signingMembers(self, pre: str):
""" Find signing members of a multisig group aid.
Using the pubs index to find members of a signing group
Parameters:
pre (str): qb64 identifier prefix to find members
Returns:
list: qb64 identifier prefixes of signing members for provided aid
"""
members = []
if pre not in self.kevers:
return members
kever = self.kevers[pre]
for verfer in kever.verfers:
if (couples := self.pubs.get(keys=(verfer.qb64,))) is None:
continue
for couple in couples:
prefixer, seqner = couple
if prefixer.qb64 != pre: # Rule out aid being queried
members.append(prefixer.qb64)
return members
[docs]
def rotationMembers(self, pre: str):
""" Find rotation members of a multisig group aid.
Using the digs index to lookup member pres of a group aid
Parameters:
pre (str): qb64 identifier prefix to find members
Returns:
list: qb64 identifier prefixes of rotation members for provided aid
"""
members = []
if pre not in self.kevers:
return members
kever = self.kevers[pre]
for diger in kever.ndigers:
if (couples := self.digs.get(keys=(diger.qb64,))) is None:
continue
for couple in couples:
prefixer, seqner = couple
if prefixer.qb64 != pre: # Rule out aid being queried
members.append(prefixer.qb64)
return members
[docs]
def fullyWitnessed(self, serder):
""" Verify the witness threshold on the event
Parameters:
serder (Serder): event serder to validate witness threshold
Returns:
"""
# Verify fully receipted, because this witness may have persisted before all receipts
# have been gathered if this ius a witness for serder.pre
dgkey = dbing.dgKey(serder.preb, serder.saidb)
# get unique verified wigers and windices lists from wigers list
wigs = self.getWigs(key=dgkey)
kever = self.kevers[serder.pre]
toad = kever.toader.num
return not len(wigs) < toad
[docs]
def resolveVerifiers(self, pre=None, sn=0, dig=None):
"""
Returns the Tholder and Verfers for the provided identifier prefix.
Default pre is own .pre
Parameters:
pre(str) is qb64 str of bytes of identifier prefix.
sn(int) is the sequence number of the est event
dig(str) is qb64 str of digest of est event
"""
prefixer = coring.Prefixer(qb64=pre)
if prefixer.transferable:
# receipted event and receipter in database so get receipter est evt
# retrieve dig of last event at sn of est evt of receipter.
sdig = self.getKeLast(key=dbing.snKey(pre=prefixer.qb64b,
sn=sn))
if sdig is None:
# receipter's est event not yet in receipters's KEL
raise kering.ValidationError("key event sn {} for pre {} is not yet in KEL"
"".format(sn, pre))
# retrieve last event itself of receipter est evt from sdig
sraw = self.getEvt(key=dbing.dgKey(pre=prefixer.qb64b, dig=bytes(sdig)))
# assumes db ensures that sraw must not be none because sdig was in KE
sserder = serdering.SerderKERI(raw=bytes(sraw))
if dig is not None and not sserder.compare(said=dig): # endorser's dig not match event
raise kering.ValidationError("Bad proof sig group at sn = {}"
" for ksn = {}."
"".format(sn, sserder.sad))
verfers = sserder.verfers
tholder = sserder.tholder
else:
verfers = [coring.Verfer(qb64=pre)]
tholder = coring.Tholder(sith="1")
return tholder, verfers
[docs]
def putEvt(self, key, val):
"""
Use dgKey()
Write serialized event bytes val to key
Does not overwrite existing val if any
Returns True If val successfully written Else False
Return False if key already exists
"""
return self.putVal(self.evts, key, val)
[docs]
def setEvt(self, key, val):
"""
Use dgKey()
Write serialized event bytes val to key
Overwrites existing val if any
Returns True If val successfully written Else False
"""
return self.setVal(self.evts, key, val)
[docs]
def getEvt(self, key):
"""
Use dgKey()
Return event at key
Returns None if no entry at key
"""
return self.getVal(self.evts, key)
[docs]
def delEvt(self, key):
"""
Use dgKey()
Deletes value at key.
Returns True If key exists in database Else False
"""
return self.delVal(self.evts, key)
[docs]
def getEvtPreIter(self, pre, sn=0):
"""
Returns iterator of event messages without attachments
in sn order from the KEL of identifier prefix pre.
Essentially a replay of all event messages without attachments
for each sn from the KEL of pre including superseded duplicates
Parameters:
pre (bytes|str): identifier prefix
sn (int): sequence number (default 0) to begin interation
"""
if hasattr(pre, 'encode'):
pre = pre.encode("utf-8")
for dig in self.getKelIter(pre, sn=sn):
try:
dgkey = dbing.dgKey(pre, dig) # get message
if not (raw := self.getEvt(key=dgkey)):
raise kering.MissingEntryError("Missing event for dig={}.".format(dig))
except Exception:
continue # skip this event
yield raw # event message
[docs]
def getEvtLastPreIter(self, pre, sn=0):
"""
Returns iterator of event messages without attachments
in sn order from the KEL of identifier prefix pre.
Essentially a replay of all event messages without attachments
for each sn from the KEL of pre including superseded duplicates
Parameters:
pre (bytes|str): identifier prefix
sn (int): sequence number (default 0) to begin interation
"""
if hasattr(pre, 'encode'):
pre = pre.encode("utf-8")
for dig in self.getKelLastIter(pre, sn=sn):
try:
dgkey = dbing.dgKey(pre, dig) # get message
if not (raw := self.getEvt(key=dgkey)):
raise kering.MissingEntryError("Missing event for dig={}.".format(dig))
except Exception:
continue # skip this event
yield raw # event message
[docs]
def putFe(self, key, val):
"""
Use fnKey()
Write event digest bytes val to key
Does not overwrite existing val if any
Returns True If val successfully written Else False
Return False if key already exists
"""
return self.putVal(self.fels, key, val)
[docs]
def setFe(self, key, val):
"""
Use fnKey()
Write event digest bytes val to key
Overwrites existing val if any
Returns True If val successfully written Else False
"""
return self.setVal(self.fels, key, val)
[docs]
def getFe(self, key):
"""
Use fnKey()
Return event digest at key
Returns None if no entry at key
"""
return self.getVal(self.fels, key)
[docs]
def delFe(self, key):
"""
Use snKey()
Deletes value at key.
Returns True If key exists in database Else False
"""
return self.delVal(self.fels, key)
[docs]
def appendFe(self, pre, val):
"""
Return first seen order number int, fn, of appended entry.
Computes fn as next fn after last entry.
Uses fnKey(pre, fn) for entries.
Append val to end of db entries with same pre but with fn incremented by
1 relative to last preexisting entry at pre.
Parameters:
pre is bytes identifier prefix for event
val is event digest
"""
return self.appendOrdValPre(db=self.fels, pre=pre, val=val)
[docs]
def getFelItemPreIter(self, pre, fn=0):
"""
Returns iterator of all (fn, dig) duples in first seen order for all events
with same prefix, pre, in database. Items are sorted by fnKey(pre, fn)
where fn is first seen order number int.
Returns a First Seen Event Log FEL.
Returned items are duples of (fn, dig): Where fn is first seen order
number int and dig is event digest for lookup in .evts sub db.
Raises StopIteration Error when empty.
Parameters:
pre is bytes of itdentifier prefix
fn is int fn to resume replay. Earliset is fn=0
"""
return self.getAllOrdItemPreIter(db=self.fels, pre=pre, on=fn)
[docs]
def getFelItemAllPreIter(self, key=b''):
"""
Returns iterator of all (pre, fn, dig) triples in first seen order for
all events for all prefixes in database. Items are sorted by
fnKey(pre, fn) where fn is first seen order number int.
Returns all First Seen Event Logs FELs.
Returned items are tripes of (pre, fn, dig): Where pre is identifier prefix,
fn is first seen order number int and dig is event digest for lookup
in .evts sub db.
Raises StopIteration Error when empty.
Parameters:
key is key location in db to resume replay, If empty then start at
first key in database
"""
return self.getAllOrdItemAllPreIter(db=self.fels, key=key)
[docs]
def putDts(self, key, val):
"""
Use dgKey()
Write serialized event datetime stamp val to key
Does not overwrite existing val if any
Returns True If val successfully written Else False
Returns False if key already exists
"""
return self.putVal(self.dtss, key, val)
[docs]
def setDts(self, key, val):
"""
Use dgKey()
Write serialized event datetime stamp val to key
Overwrites existing val if any
Returns True If val successfully written Else False
"""
return self.setVal(self.dtss, key, val)
[docs]
def getDts(self, key):
"""
Use dgKey()
Return datetime stamp at key
Returns None if no entry at key
"""
return self.getVal(self.dtss, key)
[docs]
def delDts(self, key):
"""
Use dgKey()
Deletes value at key.
Returns True If key exists in database Else False
"""
return self.delVal(self.dtss, key)
[docs]
def putAes(self, key, val):
"""
Use dgKey()
Write serialized source seal event couple val to key
Does not overwrite existing val if any
Returns True If val successfully written Else False
Returns False if key already exists
"""
return self.putVal(self.aess, key, val)
[docs]
def setAes(self, key, val):
"""
Use dgKey()
Write serialized source seal event couple val to key
Overwrites existing val if any
Returns True If val successfully written Else False
"""
return self.setVal(self.aess, key, val)
[docs]
def getAes(self, key):
"""
Use dgKey()
Return source seal event couple at key
Returns None if no entry at key
"""
return self.getVal(self.aess, key)
[docs]
def delAes(self, key):
"""
Use dgKey()
Deletes value at key.
Returns True If key exists in database Else False
"""
return self.delVal(self.aess, key)
[docs]
def getSigs(self, key):
"""
Use dgKey()
Return list of signatures at key
Returns empty list if no entry at key
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getVals(self.sigs, key)
[docs]
def getSigsIter(self, key):
"""
Use dgKey()
Return iterator of signatures at key
Raises StopIteration Error when empty
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getValsIter(self.sigs, key)
[docs]
def putSigs(self, key, vals):
"""
Use dgKey()
Write each entry from list of bytes signatures vals to key
Adds to existing signatures at key if any
Returns True If no error
Apparently always returns True (is this how .put works with dupsort=True)
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.putVals(self.sigs, key, vals)
[docs]
def addSig(self, key, val):
"""
Use dgKey()
Add signature val bytes as dup to key in db
Adds to existing values at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.addVal(self.sigs, key, val)
[docs]
def cntSigs(self, key):
"""
Use dgKey()
Return count of signatures at key
Returns zero if no entry at key
"""
return self.cntVals(self.sigs, key)
[docs]
def delSigs(self, key, val=b''):
"""
Use dgKey()
Deletes all values at key if val = b'' else deletes dup val = val.
Returns True If key exists in database (or key, val if val not b'') Else False
"""
return self.delVals(self.sigs, key, val)
[docs]
def getWigs(self, key):
"""
Use dgKey()
Return list of indexed witness signatures at key
Returns empty list if no entry at key
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getVals(self.wigs, key)
[docs]
def getWigsIter(self, key):
"""
Use dgKey()
Return iterator of indexed witness signatures at key
Raises StopIteration Error when empty
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getValsIter(self.wigs, key)
[docs]
def putWigs(self, key, vals):
"""
Use dgKey()
Write each entry from list of bytes indexed witness signatures vals to key
Adds to existing signatures at key if any
Returns True If no error
Apparently always returns True (is this how .put works with dupsort=True)
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.putVals(self.wigs, key, vals)
[docs]
def addWig(self, key, val):
"""
Use dgKey()
Add indexed witness signature val bytes as dup to key in db
Adds to existing values at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.addVal(self.wigs, key, val)
[docs]
def cntWigs(self, key):
"""
Use dgKey()
Return count of indexed witness signatures at key
Returns zero if no entry at key
"""
return self.cntVals(self.wigs, key)
[docs]
def delWigs(self, key, val=b''):
"""
Use dgKey()
Deletes all values at key if val = b'' else deletes dup val = val.
Returns True If key exists in database (or key, val if val not b'') Else False
"""
return self.delVals(self.wigs, key, val)
[docs]
def putRcts(self, key, vals):
"""
Use dgKey()
Write each entry from list of bytes receipt couplets vals to key
Couple is pre+cig (non indexed signature)
Adds to existing receipts at key if any
Returns True If no error
Apparently always returns True (is this how .put works with dupsort=True)
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.putVals(self.rcts, key, vals)
[docs]
def addRct(self, key, val):
"""
Use dgKey()
Add receipt couple val bytes as dup to key in db
Couple is pre+cig (non indexed signature)
Adds to existing values at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.addVal(self.rcts, key, val)
[docs]
def getRcts(self, key):
"""
Use dgKey()
Return list of receipt couplets at key
Couple is pre+cig (non indexed signature)
Returns empty list if no entry at key
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getVals(self.rcts, key)
[docs]
def getRctsIter(self, key):
"""
Use dgKey()
Return iterator of receipt couplets at key
Couple is pre+cig (non indexed signature)
Raises StopIteration Error when empty
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getValsIter(self.rcts, key)
[docs]
def cntRcts(self, key):
"""
Use dgKey()
Return count of receipt couplets at key
Couple is pre+cig (non indexed signature)
Returns zero if no entry at key
"""
return self.cntVals(self.rcts, key)
[docs]
def delRcts(self, key, val=b''):
"""
Use dgKey()
Deletes all values at key if val = b'' else deletes dup val = val.
Returns True If key exists in database (or key, val if val not b'') Else False
"""
return self.delVals(self.rcts, key, val)
[docs]
def putUres(self, key, vals):
"""
Use snKey()
Write each entry from list of bytes receipt triples vals to key
Triple is dig+pre+cig
Adds to existing receipts at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.ures, key, vals)
[docs]
def addUre(self, key, val):
"""
Use snKey()
Add receipt triple val bytes as dup to key in db
Triple is dig+pre+cig
Adds to existing values at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.ures, key, val)
[docs]
def getUres(self, key):
"""
Use snKey()
Return list of receipt triplets at key
Triple is dig+pre+cig
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.ures, key)
[docs]
def getUresIter(self, key):
"""
Use snKey()
Return iterator of receipt triplets at key
Triple is dig+pre+cig
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoValsIter(self.ures, key)
[docs]
def getUreLast(self, key):
"""
Use snKey()
Return last inserted dup partial signed escrowed event triple val at key
Triple is dig+pre+cig
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.ures, key)
[docs]
def getUreItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of partial signed escrowed event triple items at next
key after key.
Item is (key, val) where proem has already been stripped from val
val is triple dig+pre+cig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.ures, key, skip)
[docs]
def getUreItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of partial signed escrowed event triple items at next
key after key.
Items is (key, val) where proem has already been stripped from val
val is triple dig+pre+cig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.ures, key, skip)
[docs]
def cntUres(self, key):
"""
Use snKey()
Return count of receipt triplets at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.ures, key)
[docs]
def delUres(self, key):
"""
Use snKey()
Deletes all values at key in db.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.ures, key)
[docs]
def delUre(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.ures, key, val)
[docs]
def putVrcs(self, key, vals):
"""
Use dgKey()
Write each entry from list of bytes receipt quadruples vals to key
quadruple is spre+ssnu+sdig+sig
Adds to existing receipts at key if any
Returns True If no error
Apparently always returns True (is this how .put works with dupsort=True)
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.putVals(self.vrcs, key, vals)
[docs]
def addVrc(self, key, val):
"""
Use dgKey()
Add receipt quadruple val bytes as dup to key in db
quadruple is spre+ssnu+sdig+sig
Adds to existing values at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in lexocographic order not insertion order.
"""
return self.addVal(self.vrcs, key, val)
[docs]
def getVrcs(self, key):
"""
Use dgKey()
Return list of receipt quadruples at key
quadruple is spre+ssnu+sdig+sig
Returns empty list if no entry at key
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getVals(self.vrcs, key)
[docs]
def getVrcsIter(self, key):
"""
Use dgKey()
Return iterator of receipt quadruples at key
quadruple is spre+ssnu+sdig+sig
Raises StopIteration Error when empty
Duplicates are retrieved in lexocographic order not insertion order.
"""
return self.getValsIter(self.vrcs, key)
[docs]
def cntVrcs(self, key):
"""
Use dgKey()
Return count of receipt quadruples at key
Returns zero if no entry at key
"""
return self.cntVals(self.vrcs, key)
[docs]
def delVrcs(self, key, val=b''):
"""
Use dgKey()
Deletes all values at key if val = b'' else deletes dup val = val.
Returns True If key exists in database (or key, val if val not b'') Else False
"""
return self.delVals(self.vrcs, key, val)
[docs]
def putVres(self, key, vals):
"""
Use snKey()
Write each entry from list of bytes receipt quinlets vals to key
Quinlet is edig + spre + ssnu + sdig +sig
Adds to existing receipts at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.vres, key, vals)
[docs]
def addVre(self, key, val):
"""
Use snKey()
Add receipt quintuple val bytes as dup to key in db
Quinlet is edig + spre + ssnu + sdig +sig
Adds to existing values at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.vres, key, val)
[docs]
def getVres(self, key):
"""
Use snKey()
Return list of receipt quinlets at key
Quinlet is edig + spre + ssnu + sdig +sig
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.vres, key)
[docs]
def getVresIter(self, key):
"""
Use snKey()
Return iterator of receipt quinlets at key
Quinlet is edig + spre + ssnu + sdig +sig
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoValsIter(self.vres, key)
[docs]
def getVreLast(self, key):
"""
Use snKey()
Return last inserted dup partial signed escrowed event quintuple val at key
Quinlet is edig + spre + ssnu + sdig +sig
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.vres, key)
[docs]
def getVreItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of partial signed escrowed event quintuple items at next
key after key.
Item is (key, val) where proem has already been stripped from val
val is Quinlet is edig + spre + ssnu + sdig +sig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.vres, key, skip)
[docs]
def getVreItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of partial signed escrowed event quintuple items at next
key after key.
Items is (key, val) where proem has already been stripped from val
val is Quinlet is edig + spre + ssnu + sdig +sig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.vres, key, skip)
[docs]
def cntVres(self, key):
"""
Use snKey()
Return count of receipt quinlets at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.vres, key)
[docs]
def delVres(self, key):
"""
Use snKey()
Deletes all values at key in db.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.vres, key)
[docs]
def delVre(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.vres, key, val)
[docs]
def putKes(self, key, vals):
"""
Use snKey()
Write each key event dig entry from list of bytes vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.kels, key, vals)
[docs]
def addKe(self, key, val):
"""
Use snKey()
Add key event val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.kels, key, val)
[docs]
def getKes(self, key):
"""
Use snKey()
Return list of key event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.kels, key)
[docs]
def getKeLast(self, key):
"""
Use snKey()
Return last inserted dup key event dig vals at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.kels, key)
[docs]
def cntKes(self, key):
"""
Use snKey()
Return count of dup key event dig val at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.kels, key)
[docs]
def delKes(self, key):
"""
Use snKey()
Deletes all values at key.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.kels, key)
[docs]
def getKelIter(self, pre, sn=0):
"""
Returns iterator of all dup vals in insertion order for all entries
with same prefix across all sequence numbers without gaps. Stops if
encounters gap.
Assumes that key is combination of prefix and sequence number given
by .snKey().
db .kels values are digests used to lookup event in .evts sub DB
Raises StopIteration Error when empty.
Duplicates are retrieved in insertion order.
db is opened as named sub db with dupsort=True
Parameters:
pre (bytes | str): of itdentifier prefix prepended to sn in key
within sub db's keyspace
sn (int): initial sequence number to begin at
"""
if hasattr(pre, "encode"):
pre = pre.encode("utf-8") # convert str to bytes
return self.getIoValsAllPreIter(self.kels, pre, on=sn)
[docs]
def getKelBackIter(self, pre, sn=0):
"""
Returns iterator of all dup vals in insertion order for all entries
with same prefix across all sequence numbers without gaps in decreasing
order starting with first sequence number sn. Stops if encounters gap.
Assumes that key is combination of prefix and sequence number given
by .snKey().
db .kels values are digests used to lookup event in .evts sub DB
Raises StopIteration Error when empty.
Duplicates are retrieved in insertion order.
db is opened as named sub db with dupsort=True
Parameters:
pre (bytes | str): of itdentifier prefix prepended to sn in key
within sub db's keyspace
sn (int):
"""
if hasattr(pre, "encode"):
pre = pre.encode("utf-8") # convert str to bytes
return self.getIoValsAllPreBackIter(self.kels, pre, sn)
[docs]
def getKelLastIter(self, pre, sn=0):
"""
Returns iterator of last one of dup vals at each key in insertion order
for all entries with same prefix across all sequence numbers without gaps.
Stops if encounters gap.
Assumes that key is combination of prefix and sequence number given
by .snKey().
db .kels values are digests used to lookup event in .evts sub DB
Raises StopIteration Error when empty.
Duplicates are retrieved in insertion order.
db is opened as named sub db with dupsort=True
Parameters:
pre (bytes | str): of itdentifier prefix prepended to sn in key
within sub db's keyspace
sn (int); sequence number to being iteration
"""
if hasattr(pre, "encode"):
pre = pre.encode("utf-8") # convert str to bytes
return self.getIoValLastAllPreIter(self.kels, pre, on=sn)
[docs]
def putPses(self, key, vals):
"""
Use snKey()
Write each partial signed escrow event entry from list of bytes dig vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.pses, key, vals)
[docs]
def addPse(self, key, val):
"""
Use snKey()
Add Partial signed escrow val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.pses, key, val)
[docs]
def getPses(self, key):
"""
Use snKey()
Return list of partial signed escrowed event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.pses, key)
[docs]
def getPsesIter(self, key):
"""
Use sgKey()
Return iterator of partial signed escrowed event dig vals at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoValsIter(self.pses, key)
[docs]
def getPseLast(self, key):
"""
Use snKey()
Return last inserted dup partial signed escrowed event dig val at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.pses, key)
[docs]
def getPseItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of partial signed escrowed event dig items at next key after key.
Item is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.pses, key, skip)
[docs]
def getPseItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of partial signed escrowed event dig items at next key after key.
Items is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.pses, key, skip)
[docs]
def cntPses(self, key):
"""
Use snKey()
Return count of dup event dig vals at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.pses, key)
[docs]
def delPses(self, key):
"""
Use snKey()
Deletes all values at key in db.
Returns True If key exists in db Else False
"""
return self.delIoVals(self.pses, key)
[docs]
def delPse(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.pses, key, val)
[docs]
def putPde(self, key, val):
"""
Use dgKey()
Write serialized event source couple to key (snu+dig)
Does not overwrite existing val if any
Returns True If val successfully written Else False
Returns False if key already exists
"""
return self.putVal(self.pdes, key, val)
[docs]
def setPde(self, key, val):
"""
Use dgKey()
Write serialized seal source couple to key (snu+dig)
Overwrites existing val if any
Returns True If val successfully written Else False
"""
return self.setVal(self.pdes, key, val)
[docs]
def getPde(self, key):
"""
Use dgKey()
Return seal source couple at key
Returns None if no entry at key
"""
return self.getVal(self.pdes, key)
[docs]
def delPde(self, key):
"""
Use dgKey()
Deletes value at key.
Returns True If key exists in database Else False
"""
return self.delVal(self.pdes, key)
[docs]
def putPwes(self, key, vals):
"""
Use snKey()
Write each partial witnessed escrow event entry from list of bytes dig vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.pwes, key, vals)
[docs]
def addPwe(self, key, val):
"""
Use snKey()
Add Partial witnessed escrow dig val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.pwes, key, val)
[docs]
def getPwes(self, key):
"""
Use snKey()
Return list of witnessed signed escrowed event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.pwes, key)
[docs]
def getPwesIter(self, key):
"""
Use sgKey()
Return iterator of partial witnessed escrowed event dig vals at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoValsIter(self.pwes, key)
[docs]
def getPweLast(self, key):
"""
Use snKey()
Return last inserted dup partial witnessed escrowed event dig val at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.pwes, key)
[docs]
def getPweItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of partial witnessed escrowed event dig items at next key after key.
Item is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.pwes, key, skip)
[docs]
def getPweItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of partial witnessed escrowed event dig items at next key after key.
Items is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.pwes, key, skip)
[docs]
def cntPwes(self, key):
"""
Use snKey()
Return count of dup event dig vals at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.pwes, key)
[docs]
def delPwes(self, key):
"""
Use snKey()
Deletes all values at key in db.
Returns True If key exists in db Else False
"""
return self.delIoVals(self.pwes, key)
[docs]
def delPwe(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.pwes, key, val)
[docs]
def putUwes(self, key, vals):
"""
Use snKey()
Write each entry from list of bytes witness receipt couples vals to key
Witness couple is edig+wig
Adds to existing receipts at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.uwes, key, vals)
[docs]
def addUwe(self, key, val):
"""
Use snKey()
Add receipt couple val bytes as dup to key in db
Witness couple is edig+wig
Adds to existing values at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.uwes, key, val)
[docs]
def getUwes(self, key):
"""
Use snKey()
Return list of receipt couples at key
Witness couple is edig+wig
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.uwes, key)
[docs]
def getUwesIter(self, key):
"""
Use snKey()
Return iterator of receipt couples at key
Witness couple is edig+wig
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoValsIter(self.uwes, key)
[docs]
def getUweLast(self, key):
"""
Use snKey()
Return last inserted dup partial signed escrowed receipt couple val at key
Witness couple is edig+wig
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.uwes, key)
[docs]
def getUweItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of partial signed escrowed receipt couple items at next
key after key.
Item is (key, val) where proem has already been stripped from val
val is couple edig+wig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.uwes, key, skip)
[docs]
def getUweItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of partial signed escrowed receipt couple items at next
key after key.
Items is (key, val) where proem has already been stripped from val
val is couple edig+wig
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.uwes, key, skip)
[docs]
def cntUwes(self, key):
"""
Use snKey()
Return count of receipt couples at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.uwes, key)
[docs]
def delUwes(self, key):
"""
Use snKey()
Deletes all values at key in db.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.uwes, key)
[docs]
def delUwe(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.uwes, key, val)
[docs]
def putOoes(self, key, vals):
"""
Use snKey()
Write each out of order escrow event dig entry from list of bytes vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.ooes, key, vals)
[docs]
def addOoe(self, key, val):
"""
Use snKey()
Add out of order escrow val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.ooes, key, val)
[docs]
def getOoes(self, key):
"""
Use snKey()
Return list of out of order escrow event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.ooes, key)
[docs]
def getOoeLast(self, key):
"""
Use snKey()
Return last inserted dup val of out of order escrow event dig vals at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.ooes, key)
[docs]
def getOoeItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of out of order escrowed event dig items at next key after key.
Item is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.ooes, key, skip)
[docs]
def getOoeItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of out of order escrowed event dig items at next key after key.
Items is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.ooes, key, skip)
[docs]
def cntOoes(self, key):
"""
Use snKey()
Return count of dup event dig at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.ooes, key)
[docs]
def delOoes(self, key):
"""
Use snKey()
Deletes all values at key.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.ooes, key)
[docs]
def delOoe(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
db is opened named sub db with dupsort=True
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.ooes, key, val)
[docs]
def putQnfs(self, key, vals):
"""
Use snKey()
Write each out of order escrow event dig entry from list of bytes vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.qnfs, key, vals)
[docs]
def addQnf(self, key, val):
"""
Use snKey()
Add out of order escrow val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.qnfs, key, val)
[docs]
def getQnfs(self, key):
"""
Use snKey()
Return list of out of order escrow event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.qnfs, key)
[docs]
def getQnfLast(self, key):
"""
Use snKey()
Return last inserted dup val of out of order escrow event dig vals at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.qnfs, key)
[docs]
def getQnfItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of out of order escrowed event dig items at next key after key.
Item is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.qnfs, key, skip)
[docs]
def getQnfItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of out of order escrowed event dig items at next key after key.
Items is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.qnfs, key, skip)
[docs]
def cntQnfs(self, key):
"""
Use snKey()
Return count of dup event dig at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.qnfs, key)
[docs]
def delQnfs(self, key):
"""
Use snKey()
Deletes all values at key.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.qnfs, key)
[docs]
def delQnf(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
db is opened named sub db with dupsort=True
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.qnfs, key, val)
[docs]
def putDes(self, key, vals):
"""
Use snKey()
Write each duplicitous event entry dig from list of bytes vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.dels, key, vals)
[docs]
def addDe(self, key, val):
"""
Use snKey()
Add duplicate event index val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.dels, key, val)
[docs]
def getDes(self, key):
"""
Use snKey()
Return list of duplicitous event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.dels, key)
[docs]
def getDeLast(self, key):
"""
Use snKey()
Return last inserted dup value of duplicitous event dig vals at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.dels, key)
[docs]
def cntDes(self, key):
"""
Use snKey()
Return count of dup event dig vals at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.dels, key)
[docs]
def delDes(self, key):
"""
Use snKey()
Deletes all values at key.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.dels, key)
[docs]
def getDelIter(self, pre):
"""
Returns iterator of all dup vals in insertion order for any entries
with same prefix across all sequence numbers including gaps.
Assumes that key is combination of prefix and sequence number given
by .snKey().
Raises StopIteration Error when empty.
Duplicates are retrieved in insertion order.
Parameters:
db is opened named sub db with dupsort=True
pre is bytes of itdentifier prefix prepended to sn in key
within sub db's keyspace
"""
if hasattr(pre, "encode"):
pre = pre.encode("utf-8") # convert str to bytes
return self.getIoValsAnyPreIter(self.dels, pre)
[docs]
def putLdes(self, key, vals):
"""
Use snKey()
Write each likely duplicitous event entry dig from list of bytes vals to key
Adds to existing event indexes at key if any
Returns True If at least one of vals is added as dup, False otherwise
Duplicates are inserted in insertion order.
"""
return self.putIoVals(self.ldes, key, vals)
[docs]
def addLde(self, key, val):
"""
Use snKey()
Add likely duplicitous escrow val bytes as dup to key in db
Adds to existing event indexes at key if any
Returns True if written else False if dup val already exists
Duplicates are inserted in insertion order.
"""
return self.addIoVal(self.ldes, key, val)
[docs]
def getLdes(self, key):
"""
Use snKey()
Return list of likely duplicitous event dig vals at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoVals(self.ldes, key)
[docs]
def getLdeLast(self, key):
"""
Use snKey()
Return last inserted dup val of likely duplicitous event dig vals at key
Returns None if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoValLast(self.ldes, key)
[docs]
def getLdeItemsNext(self, key=b'', skip=True):
"""
Use snKey()
Return all dups of likely duplicitous escrowed event dig items at next key after key.
Item is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Returns empty list if no entry at key
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNext(self.ldes, key, skip)
[docs]
def getLdeItemsNextIter(self, key=b'', skip=True):
"""
Use sgKey()
Return iterator of likely duplicitous escrowed event dig items at next key after key.
Items is (key, val) where proem has already been stripped from val
If key is b'' empty then returns dup items at first key.
If skip is False and key is not b'' empty then returns dup items at key
Raises StopIteration Error when empty
Duplicates are retrieved in insertion order.
"""
return self.getIoItemsNextIter(self.ldes, key, skip)
[docs]
def cntLdes(self, key):
"""
Use snKey()
Return count of dup event dig at key
Returns zero if no entry at key
"""
return self.cntIoVals(self.ldes, key)
[docs]
def delLdes(self, key):
"""
Use snKey()
Deletes all values at key.
Returns True If key exists in database Else False
"""
return self.delIoVals(self.ldes, key)
[docs]
def delLde(self, key, val):
"""
Use snKey()
Deletes dup val at key in db.
Returns True If dup at exists in db Else False
Parameters:
db is opened named sub db with dupsort=True
key is bytes of key within sub db's keyspace
val is dup val (does not include insertion ordering proem)
"""
return self.delIoVal(self.ldes, key, val)
[docs]
class BaserDoer(doing.Doer):
"""
Basic Baser Doer ( LMDB Database )
Attributes: (inherited)
done (bool): completion state:
True means completed
Otherwise incomplete. Incompletion maybe due to close or abort.
Attributes:
.baser is Baser or LMDBer subclass
Properties: (inherited)
.tyme is float relative cycle time of associated Tymist .tyme obtained
via injected .tymth function wrapper closure.
.tymth is function wrapper closure returned by Tymist .tymeth() method.
When .tymth is called it returns associated Tymist .tyme.
.tymth provides injected dependency on Tymist tyme base.
.tock is float, desired time in seconds between runs or until next run,
non negative, zero means run asap
Properties:
Methods:
.wind injects ._tymth dependency from associated Tymist to get its .tyme
.__call__ makes instance callable
Appears as generator function that returns generator
.do is generator method that returns generator
.enter is enter context action method
.recur is recur context action method or generator method
.exit is exit context method
.close is close context method
.abort is abort context method
Hidden:
._tymth is injected function wrapper closure returned by .tymen() of
associated Tymist instance that returns Tymist .tyme. when called.
._tock is hidden attribute for .tock property
"""
[docs]
def __init__(self, baser, **kwa):
"""
Inherited Parameters:
tymist is Tymist instance
tock is float seconds initial value of .tock
Parameters:
baser is Baser instance
"""
super(BaserDoer, self).__init__(**kwa)
self.baser = baser
def enter(self):
""""""
if not self.baser.opened:
self.baser.reopen()
def exit(self):
""""""
self.baser.close(clear=self.baser.temp)