laconicd/x/registry/keeper/naming_keeper.go
Prathamesh Musale d2339e2426 Handle record and authority expiry in registry module (#7)
- Registry module
  - Update module state to track record / authority expiry queues
  - Handle expired records and authorities at the end of each block
  - Add a command to handle record renewal

Reviewed-on: deep-stack/laconic2d#7
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-02-26 06:16:09 +00:00

667 lines
19 KiB
Go

package keeper
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
"cosmossdk.io/collections"
errorsmod "cosmossdk.io/errors"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
auctiontypes "git.vdb.to/cerc-io/laconic2d/x/auction"
registrytypes "git.vdb.to/cerc-io/laconic2d/x/registry"
"git.vdb.to/cerc-io/laconic2d/x/registry/helpers"
)
// HasNameAuthority - checks if a name/authority exists.
func (k Keeper) HasNameAuthority(ctx sdk.Context, name string) (bool, error) {
has, err := k.Authorities.Has(ctx, name)
if err != nil {
return false, err
}
return has, nil
}
// GetNameAuthority - gets a name authority from the store.
func (k Keeper) GetNameAuthority(ctx sdk.Context, name string) (registrytypes.NameAuthority, error) {
authority, err := k.Authorities.Get(ctx, name)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return registrytypes.NameAuthority{}, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Name authority not found.")
}
return registrytypes.NameAuthority{}, err
}
return authority, nil
}
// ListNameAuthorityRecords - get all name authority records.
func (k Keeper) ListNameAuthorityRecords(ctx sdk.Context) (map[string]registrytypes.NameAuthority, error) {
nameAuthorityRecords := make(map[string]registrytypes.NameAuthority)
err := k.Authorities.Walk(ctx, nil, func(key string, value registrytypes.NameAuthority) (bool, error) {
nameAuthorityRecords[key] = value
return false, nil
})
if err != nil {
return map[string]registrytypes.NameAuthority{}, err
}
return nameAuthorityRecords, nil
}
// HasNameRecord - checks if a name record exists.
func (k Keeper) HasNameRecord(ctx sdk.Context, crn string) (bool, error) {
return k.NameRecords.Has(ctx, crn)
}
// GetNameRecord - gets a name record from the store.
func (k Keeper) GetNameRecord(ctx sdk.Context, crn string) (*registrytypes.NameRecord, error) {
nameRecord, err := k.NameRecords.Get(ctx, crn)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return nil, nil
}
return nil, err
}
return &nameRecord, nil
}
// LookupNameRecord - gets a name record which is not stale and under active authority.
func (k Keeper) LookupNameRecord(ctx sdk.Context, crn string) (*registrytypes.NameRecord, error) {
_, _, authority, err := k.getAuthority(ctx, crn)
if err != nil || authority.Status != registrytypes.AuthorityActive {
// If authority is not active (or any other error), lookup fails.
return nil, nil
}
nameRecord, err := k.GetNameRecord(ctx, crn)
// Name record may not exist.
if nameRecord == nil {
return nil, err
}
// Name lookup should fail if the name record is stale.
// i.e. authority was registered later than the name.
if authority.Height > nameRecord.Latest.Height {
return nil, nil
}
return nameRecord, nil
}
// ListNameRecords - get all name records.
func (k Keeper) ListNameRecords(ctx sdk.Context) ([]registrytypes.NameEntry, error) {
var nameEntries []registrytypes.NameEntry
err := k.NameRecords.Walk(ctx, nil, func(key string, value registrytypes.NameRecord) (stop bool, err error) {
nameEntries = append(nameEntries, registrytypes.NameEntry{
Name: key,
Entry: &value,
})
return false, nil
})
return nameEntries, err
}
// SaveNameRecord - sets a name record.
func (k Keeper) SaveNameRecord(ctx sdk.Context, crn string, id string) error {
var nameRecord registrytypes.NameRecord
existingNameRecord, err := k.GetNameRecord(ctx, crn)
if err != nil {
return err
}
if existingNameRecord != nil {
nameRecord = *existingNameRecord
nameRecord.History = append(nameRecord.History, nameRecord.Latest)
}
nameRecord.Latest = &registrytypes.NameRecordEntry{
Id: id,
Height: uint64(ctx.BlockHeight()),
}
// TODO: Check if index gets updated on entry updates
if err := k.NameRecords.Set(ctx, crn, nameRecord); err != nil {
return err
}
// TODO
// Update changeSet for name.
// k.updateBlockChangeSetForName(ctx, crn)
return nil
}
// SetName creates a CRN -> Record ID mapping.
func (k Keeper) SetName(ctx sdk.Context, msg registrytypes.MsgSetName) error {
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return err
}
err = k.checkCRNAccess(ctx, signerAddress, msg.Crn)
if err != nil {
return err
}
nameRecord, err := k.LookupNameRecord(ctx, msg.Crn)
if err != nil {
return err
}
if nameRecord != nil && nameRecord.Latest.Id == msg.Cid {
return nil
}
return k.SaveNameRecord(ctx, msg.Crn, msg.Cid)
}
// SaveNameAuthority creates the NameAuthority record.
func (k Keeper) SaveNameAuthority(ctx sdk.Context, name string, authority *registrytypes.NameAuthority) error {
return k.Authorities.Set(ctx, name, *authority)
// TODO
// updateBlockChangeSetForNameAuthority(ctx, codec, store, name)
}
// ReserveAuthority reserves a name authority.
func (k Keeper) ReserveAuthority(ctx sdk.Context, msg registrytypes.MsgReserveAuthority) error {
crn := fmt.Sprintf("crn://%s", msg.GetName())
parsedCrn, err := url.Parse(crn)
if err != nil {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid name")
}
name := parsedCrn.Host
if fmt.Sprintf("crn://%s", name) != crn {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid name")
}
if strings.Contains(name, ".") {
return k.ReserveSubAuthority(ctx, name, msg)
}
err = k.createAuthority(ctx, name, msg.GetSigner(), true)
if err != nil {
return err
}
return nil
}
// ReserveSubAuthority reserves a sub-authority.
func (k Keeper) ReserveSubAuthority(ctx sdk.Context, name string, msg registrytypes.MsgReserveAuthority) error {
// Get parent authority name.
names := strings.Split(name, ".")
parent := strings.Join(names[1:], ".")
// Check if parent authority exists.
if has, err := k.HasNameAuthority(ctx, parent); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Parent authority not found.")
}
parentAuthority, err := k.GetNameAuthority(ctx, parent)
if err != nil {
return err
}
// Sub-authority creator needs to be the owner of the parent authority.
if parentAuthority.OwnerAddress != msg.Signer {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Access denied.")
}
// Sub-authority owner defaults to parent authority owner.
subAuthorityOwner := msg.Signer
if len(msg.Owner) != 0 {
// Override sub-authority owner if provided in message.
subAuthorityOwner = msg.Owner
}
sdkErr := k.createAuthority(ctx, name, subAuthorityOwner, false)
if sdkErr != nil {
return sdkErr
}
return nil
}
func (k Keeper) createAuthority(ctx sdk.Context, name string, owner string, isRoot bool) error {
moduleParams, err := k.GetParams(ctx)
if err != nil {
return err
}
has, err := k.HasNameAuthority(ctx, name)
if err != nil {
return err
}
if has {
authority, err := k.GetNameAuthority(ctx, name)
if err != nil {
return err
}
if authority.Status != registrytypes.AuthorityExpired {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Name already reserved.")
}
}
ownerAddress, err := sdk.AccAddressFromBech32(owner)
if err != nil {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid owner address.")
}
ownerAccount := k.accountKeeper.GetAccount(ctx, ownerAddress)
if ownerAccount == nil {
return errorsmod.Wrap(sdkerrors.ErrUnknownAddress, "Owner account not found.")
}
authority := registrytypes.NameAuthority{
OwnerPublicKey: getAuthorityPubKey(ownerAccount.GetPubKey()),
OwnerAddress: owner,
Height: uint64(ctx.BlockHeight()),
Status: registrytypes.AuthorityActive,
AuctionId: "",
BondId: "",
ExpiryTime: ctx.BlockTime().Add(moduleParams.AuthorityGracePeriod),
}
if isRoot && moduleParams.AuthorityAuctionEnabled {
// If auctions are enabled, clear out owner fields. They will be set after a winner is picked.
authority.OwnerAddress = ""
authority.OwnerPublicKey = ""
// Reset bond ID if required.
if authority.BondId != "" || len(authority.BondId) != 0 {
// TODO
// k.RemoveBondToAuthorityIndexEntry(ctx, authority.BondId, name)
authority.BondId = ""
}
params := auctiontypes.Params{
CommitsDuration: moduleParams.AuthorityAuctionCommitsDuration,
RevealsDuration: moduleParams.AuthorityAuctionRevealsDuration,
CommitFee: moduleParams.AuthorityAuctionCommitFee,
RevealFee: moduleParams.AuthorityAuctionRevealFee,
MinimumBid: moduleParams.AuthorityAuctionMinimumBid,
}
// Create an auction.
msg := auctiontypes.NewMsgCreateAuction(params, ownerAddress)
auction, sdkErr := k.auctionKeeper.CreateAuction(ctx, msg)
if sdkErr != nil {
return sdkErr
}
// TODO
// Create auction ID -> authority name index.
// k.AddAuctionToAuthorityMapping(ctx, auction.Id, name)
authority.Status = registrytypes.AuthorityUnderAuction
authority.AuctionId = auction.Id
authority.ExpiryTime = auction.RevealsEndTime.Add(moduleParams.AuthorityGracePeriod)
}
// Save name authority in store.
if err = k.SaveNameAuthority(ctx, name, &authority); err != nil {
return err
}
return k.insertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime)
}
func (k Keeper) SetAuthorityBond(ctx sdk.Context, msg registrytypes.MsgSetAuthorityBond) error {
name := msg.GetName()
signer := msg.GetSigner()
if has, err := k.HasNameAuthority(ctx, name); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Name authority not found.")
}
authority, err := k.GetNameAuthority(ctx, name)
if err != nil {
return err
}
if authority.OwnerAddress != signer {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Access denied.")
}
if has, err := k.bondKeeper.HasBond(ctx, msg.BondId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
bond, err := k.bondKeeper.GetBondById(ctx, msg.BondId)
if err != nil {
return err
}
if bond.Owner != signer {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
}
// No-op if bond hasn't changed.
if authority.BondId == msg.BondId {
return nil
}
// TODO
// Remove old bond ID mapping, if any.
// if authority.BondId != "" {
// k.RemoveBondToAuthorityIndexEntry(ctx, authority.BondId, name)
// }
// Update bond id and save name authority in store.
authority.BondId = bond.Id
if err = k.SaveNameAuthority(ctx, name, &authority); err != nil {
return err
}
// TODO
// Add new bond ID mapping.
// k.AddBondToAuthorityIndexEntry(ctx, authority.BondId, name)
return nil
}
// DeleteName removes a CRN -> Record ID mapping.
func (k Keeper) DeleteName(ctx sdk.Context, msg registrytypes.MsgDeleteNameAuthority) error {
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return err
}
err = k.checkCRNAccess(ctx, signerAddress, msg.Crn)
if err != nil {
return err
}
crnExists, err := k.HasNameRecord(ctx, msg.Crn)
if err != nil {
return err
}
if !crnExists {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Name not found.")
}
// Set CID to empty string.
return k.SaveNameRecord(ctx, msg.Crn, "")
}
// ResolveCRN resolves a CRN to a record.
func (k Keeper) ResolveCRN(ctx sdk.Context, crn string) (*registrytypes.Record, error) {
_, _, authority, err := k.getAuthority(ctx, crn)
if err != nil || authority.Status != registrytypes.AuthorityActive {
// If authority is not active (or any other error), resolution fails.
return nil, err
}
// Name should not resolve if it's stale.
// i.e. authority was registered later than the name.
record, nameRecord, err := k.resolveCRNRecord(ctx, crn)
if err != nil {
return nil, err
}
if authority.Height > nameRecord.Latest.Height {
return nil, nil
}
return record, nil
}
func (k Keeper) resolveCRNRecord(ctx sdk.Context, crn string) (*registrytypes.Record, *registrytypes.NameRecord, error) {
nameRecord, err := k.GetNameRecord(ctx, crn)
if nameRecord == nil {
return nil, nil, err
}
latestRecordId := nameRecord.Latest.Id
if latestRecordId == "" {
return nil, nameRecord, nil
}
if has, err := k.HasRecord(ctx, latestRecordId); !has {
if err != nil {
return nil, nil, err
}
return nil, nameRecord, nil
}
record, err := k.GetRecordById(ctx, latestRecordId)
if err != nil {
return nil, nil, err
}
return &record, nameRecord, nil
}
func (k Keeper) getAuthority(ctx sdk.Context, crn string) (string, *url.URL, *registrytypes.NameAuthority, error) {
parsedCRN, err := url.Parse(crn)
if err != nil {
return "", nil, nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid CRN.")
}
name := parsedCRN.Host
if has, err := k.HasNameAuthority(ctx, name); !has {
if err != nil {
return "", nil, nil, err
}
return "", nil, nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Name authority not found.")
}
authority, err := k.GetNameAuthority(ctx, name)
if err != nil {
return "", nil, nil, err
}
return name, parsedCRN, &authority, nil
}
func (k Keeper) checkCRNAccess(ctx sdk.Context, signer sdk.AccAddress, crn string) error {
name, parsedCRN, authority, err := k.getAuthority(ctx, crn)
if err != nil {
return err
}
formattedCRN := fmt.Sprintf("crn://%s%s", name, parsedCRN.RequestURI())
if formattedCRN != crn {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid CRN.")
}
if authority.OwnerAddress != signer.String() {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Access denied.")
}
if authority.Status != registrytypes.AuthorityActive {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Authority is not active.")
}
if authority.BondId == "" || len(authority.BondId) == 0 {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Authority bond not found.")
}
if authority.OwnerPublicKey == "" {
// Try to set owner public key if account has it available now.
ownerAccount := k.accountKeeper.GetAccount(ctx, signer)
pubKey := ownerAccount.GetPubKey()
if pubKey != nil {
// Update public key in authority record.
authority.OwnerPublicKey = getAuthorityPubKey(pubKey)
if err = k.SaveNameAuthority(ctx, name, authority); err != nil {
return err
}
}
}
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 ""
}
return helpers.BytesToBase64(pubKey.Bytes())
}