Skip to content

Commit

Permalink
feat: migrates from 0.6.7 to 1.2.0
Browse files Browse the repository at this point in the history
This includes migrations 0.6.8, 1.0.0, and changes the 1.1.0 rekey_habs to 1.2.0 for clarity
  • Loading branch information
kentbull committed Oct 14, 2024
1 parent c712ba6 commit a0887ac
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 30 deletions.
6 changes: 3 additions & 3 deletions src/keri/app/cli/commands/migrate/list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- encoding: utf-8 -*-
"""
keri.kli.commands module
keri.kli.commands.migrate.list module
"""
import argparse
Expand All @@ -16,7 +16,7 @@

def handler(args):
"""
Launch KERI database initialization
List local LMDB database migrations and their completion status
Args:
args(Namespace): arguments object from command line
Expand All @@ -25,7 +25,7 @@ def handler(args):
return [lister]


parser = argparse.ArgumentParser(description='Cleans and migrates a database and keystore')
parser = argparse.ArgumentParser(description='Lists the local LMDB migrations and their completion status')
parser.set_defaults(handler=handler,
transferable=True)

Expand Down
16 changes: 7 additions & 9 deletions src/keri/app/cli/commands/migrate/run.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
# -*- encoding: utf-8 -*-
"""
keri.kli.commands module
keri.kli.commands.migrate.run module
"""
import argparse

import keri
from hio import help
from hio.base import doing
from keri import kering

from keri.app.cli.common import existing
from keri.db import basing

logger = help.ogler.getLogger()


def handler(args):
"""
Launch KERI database initialization
Launch KERI database migrator
Args:
args(Namespace): arguments object from command line
"""
clean = MigrateDoer(args)
return [clean]
migrator = MigrateDoer(args)
return [migrator]


parser = argparse.ArgumentParser(description='Cleans and migrates a database and keystore')
parser = argparse.ArgumentParser(description='Migrates a database and keystore')
parser.set_defaults(handler=handler,
transferable=True)

Expand Down Expand Up @@ -60,8 +58,8 @@ def recur(self, tyme):
except kering.DatabaseError:
pass

print("Migrating...")
print(f"Migrating {self.args.name}...")
db.migrate()
print("Finished")
print(f"Finished migrating {self.args.name}")

return True
44 changes: 31 additions & 13 deletions src/keri/db/basing.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@


MIGRATIONS = [
("1.1.0", ["rekey_habs"])
("0.6.8", ["hab_data_rename"]),
("1.0.0", ["add_key_and_reg_state_schemas"]),
("1.2.0", ["rekey_habs"])
]


Expand Down Expand Up @@ -1312,7 +1314,7 @@ def reload(self):
"""
# Check migrations to see if this database is up to date. Error otherwise
if not self.current:
raise kering.DatabaseError("Database migrations must be run.")
raise kering.DatabaseError(f"Database migrations must be run. DB version {self.version}; current {keri.__version__}")

removes = []
for keys, data in self.habs.getItemIter():
Expand Down Expand Up @@ -1346,10 +1348,18 @@ def migrate(self):
"""
for (version, migrations) in MIGRATIONS:
# Check to see if this is for an older version
# Only run migration if current source code version is at or below the migration version
ver = semver.VersionInfo.parse(keri.__version__)
ver_no_prerelease = semver.Version(ver.major, ver.minor, ver.patch)
if self.version is not None and semver.compare(version, str(ver_no_prerelease)) > 0:
print(
f"Skipping migration {version} as higher than the current KERI version {keri.__version__}")
continue
# Skip migrations already run - where version less than (-1) or equal to (0) database version
if self.version is not None and semver.compare(version, self.version) != 1:
continue

print(f"Migrating database v{self.version} --> v{version}")
for migration in migrations:
modName = f"keri.db.migrations.{migration}"
if self.migs.get(keys=(migration,)) is not None:
Expand All @@ -1360,11 +1370,14 @@ def migrate(self):
print(f"running migration {modName}")
mod.migrate(self)
except Exception as e:
print(f"\nAbandoning migration {migration} with error: {e}")
print(f"\nAbandoning migration {migration} at version {version} with error: {e}")
return

self.migs.pin(keys=(migration,), val=coring.Dater())

# update database version after successful migration
self.version = version

self.version = keri.__version__

def clearEscrows(self):
Expand Down Expand Up @@ -1407,14 +1420,16 @@ def current(self):
if self.version == keri.__version__:
return True

# If database version is ahead of library version, throw exception
if self.version is not None and semver.compare(self.version, keri.__version__) == 1:
ver = semver.VersionInfo.parse(keri.__version__)
ver_no_prerelease = semver.Version(ver.major, ver.minor, ver.patch)
if self.version is not None and semver.compare(self.version, str(ver_no_prerelease)) == 1:
raise kering.ConfigurationError(
f"Database version={self.version} is ahead of library version={keri.__version__}")

last = MIGRATIONS[-1]
# If we aren't at latest version, but there are no outstanding migrations, reset version to latest
if self.migs.get(keys=(last[1][0],)) is not None:
# If we aren't at latest version, but there are no outstanding migrations,
# reset version to latest (rightmost (-1) migration is latest)
if self.migs.get(keys=(last[1][-1],)) is not None:
return True

# We have migrations to run
Expand All @@ -1433,12 +1448,15 @@ def complete(self, name=None):
migrations = []
if not name:
for version, migs in MIGRATIONS:
for mig in migs:
dater = self.migs.get(keys=(mig,))
migrations.append((mig, dater))
# Print entries only for migrations that have been run
if self.version is not None and semver.compare(version, self.version) <= 0:
for mig in migs:
dater = self.migs.get(keys=(mig,))
migrations.append((mig, dater))
else:
if name not in MIGRATIONS or not self.migs.get(keys=(name,)):
raise ValueError(f"No migration named {name}")
for version, migs in MIGRATIONS: # check all migrations for each version
if name not in migs or not self.migs.get(keys=(name,)):
raise ValueError(f"No migration named {name}")
migrations.append((name, self.migs.get(keys=(name,))))

return migrations
Expand Down
147 changes: 147 additions & 0 deletions src/keri/db/migrations/add_key_and_reg_state_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from keri import help
from keri.core import coring, serdering
from keri.db import koming, subing, dbing
from keri.db.basing import StateEERecord, KeyStateRecord
from keri.db.dbing import dgKey, splitKey
from keri.kering import ConfigurationError, Version
from keri.vdr import viring

logger = help.ogler.getLogger()

def _check_if_needed(db):
states = koming.Komer(db=db,
schema=dict,
subkey='stts.')
first = next(states.getItemIter(), None)
if first is None:
return False
keys, sad = first
if 'vn' in sad:
return False
return True

def migrate(db):
"""Adds schema for KeyStateRecord, RegStateRecord, and migrates the rgy.cancs., hby.db.pubs.,
and hby.db.digs. to be up to date as of 2022-??-??
This migration performs the following:
- hby.db -> "stts." schema from dict -> KeyStateRecord
- rgy -> "stts." schema from dict -> RegStateRecord
- rgy -> "cancs." reset to (ACDC SAID, SN 0, TEL evt 0 digest)
- hby.db -> "pubs." and
hby.db -> "digs."
that don't exist are populated with verification keys and event digests for the first seen events and
Keys:
"pubs." Verfer of each Verfer for each FEL event
"digs." Diger of next Diger (ndiger) of each FEL event
Value: (prefix, sn) of each event
Parameters:
db(Baser): Baser database object on which to run the migration
"""
# May be running on a database that is already in the right state yet has no migrations run
# so we need to check if the migration is needed
if not _check_if_needed(db):
print(f"{__name__} migration not needed, database already in correct state")
return

try:
logger.debug(f"Migrating keystate and regstate dict to schema for {db.path}")
states = koming.Komer(db=db,
schema=dict,
subkey='stts.')
nstates = koming.Komer(db=db,
schema=KeyStateRecord,
subkey='stts.')

for keys, sad in states.getItemIter():
ksr = KeyStateRecord(
vn=Version, # version number as list [major, minor]
i=sad['i'], # qb64 prefix
s=sad['s'], # lowercase hex string no leading zeros
p=sad['p'],
d=sad['d'],
f=sad['f'], # lowercase hex string no leading zeros
dt=sad['dt'],
et=sad['et'],
kt=sad['kt'],
k=sad['k'],
nt=sad['nt'],
n=sad['n'],
bt=sad['bt'],
b=sad['b'],
c=sad['c'],
ee=StateEERecord._fromdict(sad['ee']), # latest est event dict
di=sad['di'] if sad['di'] else None
)

nstates.pin(keys=keys, val=ksr)

rgy = viring.Reger(name=db.name, base=db.base, db=db, temp=db.temp, reopen=True)

rstates = koming.Komer(db=rgy,
schema=dict,
subkey='stts.')

for _, sad in rstates.getItemIter():
rsr = viring.RegStateRecord(
vn=list(Version), # version number as list [major, minor]
i=sad['i'], # qb64 registry SAID
s=sad['s'], # lowercase hex string no leading zeros
d=sad['d'],
ii=sad['ii'],
dt=sad['dt'],
et=sad['et'],
bt=sad['bt'], # hex string no leading zeros lowercase
b=sad['b'], # list of qb64 may be empty
c=sad['c'],
)
# ksr = stateFromKever(kever)
rgy.states.pin(sad['i'], val=rsr)

for (said,), _ in rgy.saved.getItemIter():
snkey = dbing.snKey(said, 0)
dig = rgy.getTel(key=snkey)

prefixer = coring.Prefixer(qb64=said)
seqner = coring.Seqner(sn=0)
saider = coring.Saider(qb64b=bytes(dig))
rgy.cancs.pin(keys=said, val=[prefixer, seqner, saider])

migrateKeys(db)

# clear escrows
logger.info("clearing escrows")
db.gpwe.trim()
db.gdee.trim()
db.dpwe.trim()
db.gpse.trim()
db.epse.trim()
db.dune.trim()
db.qnfs.trim()

except ConfigurationError:
logger.error(f"identifier prefix for {db.name} does not exist, incept must be run first", )
return -1


def migrateKeys(db):
# public keys mapped to the AID and event seq no they appeared in
pubs = subing.CatCesrIoSetSuber(db=db, subkey="pubs.",
klas=(coring.Prefixer, coring.Seqner))

# next key digests mapped to the AID and event seq no they appeared in
digs = subing.CatCesrIoSetSuber(db=db, subkey="digs.",
klas=(coring.Prefixer, coring.Seqner))

for pre, fn, dig in db.getFelItemAllPreIter():
dgkey = dbing.dgKey(pre, dig) # get message
if not (raw := db.getEvt(key=dgkey)):
logger.info(f"Migrate keys: missing event for dig={dig}, skipped.")
continue
serder = serdering.SerderKERI(raw=bytes(raw))
val = (coring.Prefixer(qb64b=serder.preb), coring.Seqner(sn=serder.sn))
verfers = serder.verfers or []
for verfer in verfers:
pubs.add(keys=(verfer.qb64,), val=val)
ndigers = serder.ndigers or []
for diger in ndigers:
digs.add(keys=(diger.qb64,), val=val)
Loading

0 comments on commit a0887ac

Please sign in to comment.