From b3b92c7955a0b1ad8fdba35a0ac87027d6d87720 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Fri, 23 Feb 2024 15:14:05 +0530 Subject: [PATCH] Store and handle authority expirations --- x/registry/keeper/genesis.go | 11 ++- x/registry/keeper/keeper.go | 17 ++-- x/registry/keeper/naming_keeper.go | 149 ++++++++++++++++++++++++++++- x/registry/keys.go | 3 +- x/registry/module/abci.go | 11 ++- 5 files changed, 174 insertions(+), 17 deletions(-) diff --git a/x/registry/keeper/genesis.go b/x/registry/keeper/genesis.go index 4665e7a2..7d61cf5e 100644 --- a/x/registry/keeper/genesis.go +++ b/x/registry/keeper/genesis.go @@ -22,11 +22,13 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, data *registry.GenesisState) error // Add to record expiry queue if expiry time is in the future. expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime) if err != nil { - panic(err) + return err } if expiryTime.After(ctx.BlockTime()) { - k.insertRecordExpiryQueue(ctx, record) + if err := k.insertRecordExpiryQueue(ctx, record); err != nil { + return err + } } } @@ -37,9 +39,10 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, data *registry.GenesisState) error return err } - // TODO // Add authority name to expiry queue. - // k.InsertAuthorityExpiryQueue(ctx, authority.Name, authority.Entry.ExpiryTime) + if err := k.insertAuthorityExpiryQueue(ctx, authority.Name, authority.Entry.ExpiryTime); err != nil { + return err + } // TODO // Note: Bond genesis runs first, so bonds will already be present. diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 307e6612..c1224e4b 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -92,12 +92,13 @@ type Keeper struct { auctionKeeper auctionkeeper.Keeper // state management - Schema collections.Schema - Params collections.Item[registrytypes.Params] - Records *collections.IndexedMap[string, registrytypes.Record, RecordsIndexes] - Authorities *collections.IndexedMap[string, registrytypes.NameAuthority, AuthoritiesIndexes] - NameRecords *collections.IndexedMap[string, registrytypes.NameRecord, NameRecordsIndexes] - RecordExpiryQueue collections.Map[time.Time, registrytypes.ExpiryQueue] + Schema collections.Schema + Params collections.Item[registrytypes.Params] + Records *collections.IndexedMap[string, registrytypes.Record, RecordsIndexes] + Authorities *collections.IndexedMap[string, registrytypes.NameAuthority, AuthoritiesIndexes] + NameRecords *collections.IndexedMap[string, registrytypes.NameRecord, NameRecordsIndexes] + RecordExpiryQueue collections.Map[time.Time, registrytypes.ExpiryQueue] + AuthorityExpiryQueue collections.Map[time.Time, registrytypes.ExpiryQueue] } // NewKeeper creates a new Keeper instance @@ -138,6 +139,10 @@ func NewKeeper( sb, registrytypes.RecordExpiryQueuePrefix, "record_expiry_queue", sdk.TimeKey, codec.CollValue[registrytypes.ExpiryQueue](cdc), ), + AuthorityExpiryQueue: collections.NewMap( + sb, registrytypes.AuthorityExpiryQueuePrefix, "authority_expiry_queue", + sdk.TimeKey, codec.CollValue[registrytypes.ExpiryQueue](cdc), + ), } schema, err := sb.Build() diff --git a/x/registry/keeper/naming_keeper.go b/x/registry/keeper/naming_keeper.go index a0433e5f..eaf93dcd 100644 --- a/x/registry/keeper/naming_keeper.go +++ b/x/registry/keeper/naming_keeper.go @@ -5,6 +5,7 @@ import ( "fmt" "net/url" "strings" + "time" "cosmossdk.io/collections" errorsmod "cosmossdk.io/errors" @@ -318,10 +319,7 @@ func (k Keeper) createAuthority(ctx sdk.Context, name string, owner string, isRo return err } - // TODO - // k.InsertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime) - - return nil + return k.insertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime) } func (k Keeper) SetAuthorityBond(ctx sdk.Context, msg registrytypes.MsgSetAuthorityBond) error { @@ -520,6 +518,149 @@ func (k Keeper) checkCRNAccess(ctx sdk.Context, signer sdk.AccAddress, crn strin return nil } +// ProcessAuthorityExpiryQueue tries to renew expiring authorities (by collecting rent) else marks them as expired. +func (k Keeper) ProcessAuthorityExpiryQueue(ctx sdk.Context) error { + names, err := k.getAllExpiredAuthorities(ctx, ctx.BlockHeader().Time) + if err != nil { + return err + } + + for _, name := range names { + authority, err := k.GetNameAuthority(ctx, name) + if err != nil { + return err + } + + bondExists := false + if authority.BondId != "" { + bondExists, err = k.bondKeeper.HasBond(ctx, authority.BondId) + if err != nil { + return err + } + } + + // If authority doesn't have an associated bond or if bond no longer exists, mark it expired. + if !bondExists { + authority.Status = registrytypes.AuthorityExpired + if err = k.SaveNameAuthority(ctx, name, &authority); err != nil { + return err + } + + if err = k.deleteAuthorityExpiryQueue(ctx, name, authority); err != nil { + return err + } + + // TODO: Setup logger + // k.Logger(ctx).Info(fmt.Sprintf("Marking authority expired as no bond present: %s", name)) + + return nil + } + + // Try to renew the authority by taking rent. + if err := k.tryTakeAuthorityRent(ctx, name, authority); err != nil { + return err + } + } + + return nil +} + +// getAllExpiredAuthorities returns a concatenated list of all the timeslices before currTime. +func (k Keeper) getAllExpiredAuthorities(ctx sdk.Context, currTime time.Time) ([]string, error) { + var expiredAuthorityNames []string + + // Get all the authorities with expiry time until currTime + rng := new(collections.Range[time.Time]).EndInclusive(currTime) + err := k.AuthorityExpiryQueue.Walk(ctx, rng, func(key time.Time, value registrytypes.ExpiryQueue) (stop bool, err error) { + expiredAuthorityNames = append(expiredAuthorityNames, value.Value...) + return false, nil + }) + if err != nil { + return []string{}, err + } + + return expiredAuthorityNames, nil +} + +func (k Keeper) insertAuthorityExpiryQueue(ctx sdk.Context, name string, expiryTime time.Time) error { + existingNamesList, err := k.AuthorityExpiryQueue.Get(ctx, expiryTime) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + existingNamesList = registrytypes.ExpiryQueue{ + Id: expiryTime.String(), + Value: []string{}, + } + } else { + return err + } + } + + existingNamesList.Value = append(existingNamesList.Value, name) + return k.AuthorityExpiryQueue.Set(ctx, expiryTime, existingNamesList) +} + +// deleteAuthorityExpiryQueue deletes an authority name from the authority expiry queue. +func (k Keeper) deleteAuthorityExpiryQueue(ctx sdk.Context, name string, authority registrytypes.NameAuthority) error { + expiryTime := authority.ExpiryTime + existingNamesList, err := k.AuthorityExpiryQueue.Get(ctx, expiryTime) + if err != nil { + return err + } + + newNamesSlice := []string{} + for _, id := range existingNamesList.Value { + if id != name { + newNamesSlice = append(newNamesSlice, id) + } + } + + if len(existingNamesList.Value) == 0 { + return k.AuthorityExpiryQueue.Remove(ctx, expiryTime) + } else { + existingNamesList.Value = newNamesSlice + return k.AuthorityExpiryQueue.Set(ctx, expiryTime, existingNamesList) + } +} + +// tryTakeAuthorityRent tries to take rent from the authority bond. +func (k Keeper) tryTakeAuthorityRent(ctx sdk.Context, name string, authority registrytypes.NameAuthority) error { + // k.Logger(ctx).Info(fmt.Sprintf("Trying to take rent for authority: %s", name)) + + params, err := k.GetParams(ctx) + if err != nil { + return err + } + + rent := params.AuthorityRent + sdkErr := k.bondKeeper.TransferCoinsToModuleAccount(ctx, authority.BondId, registrytypes.AuthorityRentModuleAccountName, sdk.NewCoins(rent)) + if sdkErr != nil { + // Insufficient funds, mark authority as expired. + authority.Status = registrytypes.AuthorityExpired + if err := k.SaveNameAuthority(ctx, name, &authority); err != nil { + return err + } + + // k.Logger(ctx).Info(fmt.Sprintf("Insufficient funds in owner account to pay authority rent, marking as expired: %s", name)) + return k.deleteAuthorityExpiryQueue(ctx, name, authority) + } + + // Delete old expiry queue entry, create new one. + if err := k.deleteAuthorityExpiryQueue(ctx, name, authority); err != nil { + return err + } + + authority.ExpiryTime = ctx.BlockTime().Add(params.AuthorityRentDuration) + if err := k.insertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime); err != nil { + return err + } + + // Save authority. + authority.Status = registrytypes.AuthorityActive + + // k.Logger(ctx).Info(fmt.Sprintf("Authority rent paid successfully: %s", name)) + return k.SaveNameAuthority(ctx, name, &authority) +} + func getAuthorityPubKey(pubKey cryptotypes.PubKey) string { if pubKey == nil { return "" diff --git a/x/registry/keys.go b/x/registry/keys.go index 3135ca04..58e68269 100644 --- a/x/registry/keys.go +++ b/x/registry/keys.go @@ -28,5 +28,6 @@ var ( NameRecordsPrefix = collections.NewPrefix(6) NameRecordsByCidIndexPrefix = collections.NewPrefix(7) - RecordExpiryQueuePrefix = collections.NewPrefix(8) + RecordExpiryQueuePrefix = collections.NewPrefix(8) + AuthorityExpiryQueuePrefix = collections.NewPrefix(9) ) diff --git a/x/registry/module/abci.go b/x/registry/module/abci.go index f2ae1897..503ff9d1 100644 --- a/x/registry/module/abci.go +++ b/x/registry/module/abci.go @@ -13,6 +13,13 @@ func EndBlocker(ctx context.Context, k keeper.Keeper) error { // TODO: Implement sdkCtx := sdk.UnwrapSDKContext(ctx) - return k.ProcessRecordExpiryQueue(sdkCtx) - // k.ProcessAuthorityExpiryQueue(ctx) + if err := k.ProcessRecordExpiryQueue(sdkCtx); err != nil { + return err + } + + if err := k.ProcessAuthorityExpiryQueue(sdkCtx); err != nil { + return err + } + + return nil }