laconicd/x/registry/keeper/record_keeper.go
Prathamesh Musale eb20f77e83
All checks were successful
Integration Tests / test-integration (push) Successful in 3m2s
E2E Tests / test-e2e (push) Successful in 4m29s
Unit Tests / test-unit (push) Successful in 2m8s
Publish on release / Run docker build and publish (release) Successful in 2m50s
SDK Tests / sdk_tests_nameservice_expiry (push) Successful in 8m52s
SDK Tests / sdk_tests (push) Successful in 10m7s
SDK Tests / sdk_tests_auctions (push) Successful in 14m13s
Fixes for handling authority expiry queue (#40)
Part of #38

Reviewed-on: cerc-io/laconic2d#40
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-07-22 05:40:45 +00:00

343 lines
9.4 KiB
Go

package keeper
import (
"errors"
"fmt"
"time"
"cosmossdk.io/collections"
errorsmod "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
auctiontypes "git.vdb.to/cerc-io/laconicd/x/auction"
auctionkeeper "git.vdb.to/cerc-io/laconicd/x/auction/keeper"
bondtypes "git.vdb.to/cerc-io/laconicd/x/bond"
registrytypes "git.vdb.to/cerc-io/laconicd/x/registry"
)
// Record keeper implements the bond usage keeper interface.
var (
_ auctiontypes.AuctionUsageKeeper = RecordKeeper{}
_ bondtypes.BondUsageKeeper = RecordKeeper{}
)
// RecordKeeper exposes the bare minimal read-only API for other modules.
type RecordKeeper struct {
cdc codec.BinaryCodec // The wire codec for binary encoding/decoding.
k *Keeper
auctionKeeper *auctionkeeper.Keeper
}
// NewRecordKeeper creates new instances of the registry RecordKeeper
func NewRecordKeeper(cdc codec.BinaryCodec, k *Keeper, auctionKeeper *auctionkeeper.Keeper) RecordKeeper {
return RecordKeeper{
cdc: cdc,
k: k,
auctionKeeper: auctionKeeper,
}
}
// ModuleName returns the module name.
func (rk RecordKeeper) ModuleName() string {
return registrytypes.ModuleName
}
func (rk RecordKeeper) UsesAuction(ctx sdk.Context, auctionId string) bool {
iter, err := rk.k.Authorities.Indexes.AuctionId.MatchExact(ctx, auctionId)
if err != nil {
panic(err)
}
return iter.Valid()
}
func (rk RecordKeeper) OnAuctionWinnerSelected(ctx sdk.Context, auctionId string) {
// Update authority status based on auction status/winner.
iter, err := rk.k.Authorities.Indexes.AuctionId.MatchExact(ctx, auctionId)
if err != nil && !errors.Is(err, collections.ErrNotFound) {
panic(err)
}
names, err := iter.PrimaryKeys()
if err != nil {
panic(err)
}
if len(names) == 0 {
// We don't know about this auction, ignore.
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, name mapping not found: %s", auctionId))
return
}
// Take the first one as an auction (non-empty) will map to only one name
// MultiIndex being used as there can be multiple entries with empty auction id ("")
name := names[0]
if has, err := rk.k.HasNameAuthority(ctx, name); !has {
if err != nil {
panic(err)
}
// We don't know about this authority, ignore.
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, authority not found: %s", auctionId))
return
}
authority, err := rk.k.GetNameAuthority(ctx, name)
if err != nil {
panic(err)
}
auctionObj, err := rk.auctionKeeper.GetAuctionById(ctx, auctionId)
if err != nil {
panic(err)
}
if auctionObj.Status == auctiontypes.AuctionStatusCompleted {
if auctionObj.WinnerAddress != "" {
// Mark authority owner and change status to active.
authority.OwnerAddress = auctionObj.WinnerAddress
authority.Status = registrytypes.AuthorityActive
// Reset bond id if required, as owner has changed.
authority.BondId = ""
// Update height for updated/changed authority (owner).
// Can be used to check if names are older than the authority itself (stale names).
authority.Height = uint64(ctx.BlockHeight())
logger(ctx).Info(fmt.Sprintf("Winner selected, marking authority as active: %s", name))
} else {
// Mark as expired.
authority.Status = registrytypes.AuthorityExpired
logger(ctx).Info(fmt.Sprintf("No winner, marking authority as expired: %s", name))
logger(ctx).Info(fmt.Sprintf("Deleting the expiry queue entry: %s", name))
if err = rk.k.deleteAuthorityExpiryQueue(ctx, name, authority); err != nil {
logger(ctx).Error("Unable to delete expiry queue entry", err)
}
}
// Forget about this auction now, we no longer need it.
authority.AuctionId = ""
if err = rk.k.SaveNameAuthority(ctx, name, &authority); err != nil {
panic(err)
}
} else {
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, status: %s", auctionObj.Status))
}
}
// UsesBond returns true if the bond has associated records.
func (rk RecordKeeper) UsesBond(ctx sdk.Context, bondId string) bool {
iter, err := rk.k.Records.Indexes.BondId.MatchExact(ctx, bondId)
if err != nil {
panic(err)
}
return iter.Valid()
}
// RenewRecord renews a record.
func (k Keeper) RenewRecord(ctx sdk.Context, msg registrytypes.MsgRenewRecord) error {
if has, err := k.HasRecord(ctx, msg.RecordId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
}
// Check if renewal is required (i.e. expired record marked as deleted).
record, err := k.GetRecordById(ctx, msg.RecordId)
if err != nil {
return err
}
expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime)
if err != nil {
return err
}
if !record.Deleted || expiryTime.After(ctx.BlockTime()) {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Renewal not required.")
}
readableRecord := record.ToReadableRecord()
return k.processRecord(ctx, &readableRecord)
}
// AssociateBond associates a record with a bond.
func (k Keeper) AssociateBond(ctx sdk.Context, msg registrytypes.MsgAssociateBond) error {
if has, err := k.HasRecord(ctx, msg.RecordId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
}
if has, err := k.bondKeeper.HasBond(ctx, msg.BondId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
// Check if already associated with a bond.
record, err := k.GetRecordById(ctx, msg.RecordId)
if err != nil {
return err
}
if len(record.BondId) != 0 {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond already exists.")
}
// Only the bond owner can associate a record with the bond.
bond, err := k.bondKeeper.GetBondById(ctx, msg.BondId)
if err != nil {
return err
}
if msg.Signer != bond.Owner {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
}
record.BondId = msg.BondId
if err = k.SaveRecord(ctx, record); err != nil {
return err
}
// Required so that renewal is triggered (with new bond ID) for expired records.
if record.Deleted {
return k.insertRecordExpiryQueue(ctx, record)
}
return nil
}
// DissociateBond dissociates a record from its bond.
func (k Keeper) DissociateBond(ctx sdk.Context, msg registrytypes.MsgDissociateBond) error {
if has, err := k.HasRecord(ctx, msg.RecordId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
}
record, err := k.GetRecordById(ctx, msg.RecordId)
if err != nil {
return err
}
// Check if record associated with a bond.
bondId := record.BondId
if len(bondId) == 0 {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond not found.")
}
// Only the bond owner can dissociate a record with the bond.
bond, err := k.bondKeeper.GetBondById(ctx, bondId)
if err != nil {
return err
}
if msg.Signer != bond.Owner {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
}
// Clear bond Id.
record.BondId = ""
return k.SaveRecord(ctx, record)
}
// DissociateRecords dissociates all records associated with a given bond.
func (k Keeper) DissociateRecords(ctx sdk.Context, msg registrytypes.MsgDissociateRecords) error {
if has, err := k.bondKeeper.HasBond(ctx, msg.BondId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
// Only the bond owner can dissociate all records from the bond.
bond, err := k.bondKeeper.GetBondById(ctx, msg.BondId)
if err != nil {
return err
}
if msg.Signer != bond.Owner {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
}
// Dissociate all records from the bond.
records, err := k.GetRecordsByBondId(ctx, msg.BondId)
if err != nil {
return err
}
for _, record := range records {
// Clear bond Id.
record.BondId = ""
if err = k.SaveRecord(ctx, record); err != nil {
return err
}
}
return nil
}
// ReassociateRecords switches records from and old to new bond.
func (k Keeper) ReassociateRecords(ctx sdk.Context, msg registrytypes.MsgReassociateRecords) error {
if has, err := k.bondKeeper.HasBond(ctx, msg.OldBondId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Old bond not found.")
}
if has, err := k.bondKeeper.HasBond(ctx, msg.NewBondId); !has {
if err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "New bond not found.")
}
// Only the bond owner can re-associate all records.
oldBond, err := k.bondKeeper.GetBondById(ctx, msg.OldBondId)
if err != nil {
return err
}
if msg.Signer != oldBond.Owner {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Old bond owner mismatch.")
}
newBond, err := k.bondKeeper.GetBondById(ctx, msg.NewBondId)
if err != nil {
return err
}
if msg.Signer != newBond.Owner {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "New bond owner mismatch.")
}
// Re-associate all records.
records, err := k.GetRecordsByBondId(ctx, msg.OldBondId)
if err != nil {
return err
}
for _, record := range records {
// Switch bond ID.
record.BondId = msg.NewBondId
if err = k.SaveRecord(ctx, record); err != nil {
return err
}
// Required so that renewal is triggered (with new bond ID) for expired records.
if record.Deleted {
if err = k.insertRecordExpiryQueue(ctx, record); err != nil {
return err
}
}
}
return nil
}