2022-04-05 07:09:27 +00:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-04-08 07:33:58 +00:00
|
|
|
"time"
|
|
|
|
|
2023-03-15 15:22:35 +00:00
|
|
|
errorsmod "cosmossdk.io/errors"
|
2022-09-07 06:36:11 +00:00
|
|
|
auctionkeeper "github.com/cerc-io/laconicd/x/auction/keeper"
|
|
|
|
auctiontypes "github.com/cerc-io/laconicd/x/auction/types"
|
|
|
|
bondtypes "github.com/cerc-io/laconicd/x/bond/types"
|
2022-12-09 04:17:14 +00:00
|
|
|
"github.com/cerc-io/laconicd/x/registry/types"
|
2022-04-05 07:09:27 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
2022-04-23 15:53:51 +00:00
|
|
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
2022-04-05 07:09:27 +00:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RecordKeeper exposes the bare minimal read-only API for other modules.
|
|
|
|
type RecordKeeper struct {
|
|
|
|
auctionKeeper auctionkeeper.Keeper
|
2022-04-23 15:53:51 +00:00
|
|
|
storeKey storetypes.StoreKey // Unexposed key to access store from sdk.Context
|
|
|
|
cdc codec.BinaryCodec // The wire codec for binary encoding/decoding.
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (k RecordKeeper) UsesAuction(ctx sdk.Context, auctionID string) bool {
|
|
|
|
return k.GetAuctionToAuthorityMapping(ctx, auctionID) != ""
|
|
|
|
}
|
|
|
|
|
2022-10-17 11:03:31 +00:00
|
|
|
func (k RecordKeeper) OnAuction(ctx sdk.Context, auctionID string) {
|
|
|
|
updateBlockChangeSetForAuction(ctx, k, auctionID)
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (k RecordKeeper) OnAuctionBid(ctx sdk.Context, auctionID string, bidderAddress string) {
|
|
|
|
updateBlockChangeSetForAuctionBid(ctx, k, auctionID, bidderAddress)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k RecordKeeper) OnAuctionWinnerSelected(ctx sdk.Context, auctionID string) {
|
|
|
|
// Update authority status based on auction status/winner.
|
|
|
|
name := k.GetAuctionToAuthorityMapping(ctx, auctionID)
|
|
|
|
if name == "" {
|
|
|
|
// We don't know about this auction, ignore.
|
2024-01-15 04:20:38 +00:00
|
|
|
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, name mapping not found: %s", auctionID))
|
2022-04-05 07:09:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
if !HasNameAuthority(store, name) {
|
|
|
|
// We don't know about this authority, ignore.
|
2024-01-15 04:20:38 +00:00
|
|
|
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, authority not found: %s", auctionID))
|
2022-04-05 07:09:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authority := GetNameAuthority(store, k.cdc, name)
|
|
|
|
auctionObj := k.auctionKeeper.GetAuction(ctx, auctionID)
|
|
|
|
|
|
|
|
if auctionObj.Status == auctiontypes.AuctionStatusCompleted {
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
|
|
|
|
if auctionObj.WinnerAddress != "" {
|
|
|
|
// Mark authority owner and change status to active.
|
|
|
|
authority.OwnerAddress = auctionObj.WinnerAddress
|
|
|
|
authority.Status = types.AuthorityActive
|
|
|
|
|
|
|
|
// Reset bond ID if required, as owner has changed.
|
|
|
|
if authority.BondId != "" {
|
|
|
|
RemoveBondToAuthorityIndexEntry(store, authority.BondId, name)
|
|
|
|
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())
|
|
|
|
|
2024-01-15 04:20:38 +00:00
|
|
|
logger(ctx).Info(fmt.Sprintf("Winner selected, marking authority as active: %s", name))
|
2022-04-05 07:09:27 +00:00
|
|
|
} else {
|
|
|
|
// Mark as expired.
|
|
|
|
authority.Status = types.AuthorityExpired
|
|
|
|
|
2024-01-15 04:20:38 +00:00
|
|
|
logger(ctx).Info(fmt.Sprintf("No winner, marking authority as expired: %s", name))
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
authority.AuctionId = ""
|
|
|
|
SetNameAuthority(ctx, store, k.cdc, name, &authority)
|
|
|
|
|
|
|
|
// Forget about this auction now, we no longer need it.
|
|
|
|
removeAuctionToAuthorityMapping(store, auctionID)
|
|
|
|
} else {
|
2024-01-15 04:20:38 +00:00
|
|
|
logger(ctx).Info(fmt.Sprintf("Ignoring auction notification, status: %s", auctionObj.Status))
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Record keeper implements the bond usage keeper interface.
|
2022-10-17 06:47:56 +00:00
|
|
|
var (
|
|
|
|
_ bondtypes.BondUsageKeeper = (*RecordKeeper)(nil)
|
|
|
|
_ auctiontypes.AuctionUsageKeeper = (*RecordKeeper)(nil)
|
|
|
|
)
|
2022-04-05 07:09:27 +00:00
|
|
|
|
|
|
|
// ModuleName returns the module name.
|
|
|
|
func (k RecordKeeper) ModuleName() string {
|
|
|
|
return types.ModuleName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k RecordKeeper) GetAuctionToAuthorityMapping(ctx sdk.Context, auctionID string) string {
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
|
|
|
|
auctionToAuthorityIndexKey := GetAuctionToAuthorityIndexKey(auctionID)
|
|
|
|
if store.Has(auctionToAuthorityIndexKey) {
|
|
|
|
bz := store.Get(auctionToAuthorityIndexKey)
|
|
|
|
return string(bz)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// UsesBond returns true if the bond has associated records.
|
2022-10-17 11:03:31 +00:00
|
|
|
func (k RecordKeeper) UsesBond(ctx sdk.Context, bondID string) bool {
|
|
|
|
bondIDPrefix := append(PrefixBondIDToRecordsIndex, []byte(bondID)...) //nolint: all
|
2022-04-05 07:09:27 +00:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
itr := sdk.KVStorePrefixIterator(store, bondIDPrefix)
|
|
|
|
defer itr.Close()
|
|
|
|
return itr.Valid()
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveBondToRecordIndexEntry removes the Bond ID -> [Record] index entry.
|
|
|
|
func (k Keeper) RemoveBondToRecordIndexEntry(ctx sdk.Context, bondID string, id string) {
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
store.Delete(getBondIDToRecordsIndexKey(bondID, id))
|
|
|
|
}
|
|
|
|
|
2022-12-09 04:17:14 +00:00
|
|
|
// NewRecordKeeper creates new instances of the registry RecordKeeper
|
2022-04-23 15:53:51 +00:00
|
|
|
func NewRecordKeeper(auctionKeeper auctionkeeper.Keeper, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) RecordKeeper {
|
2022-04-05 07:09:27 +00:00
|
|
|
return RecordKeeper{
|
|
|
|
auctionKeeper: auctionKeeper,
|
|
|
|
storeKey: storeKey,
|
|
|
|
cdc: cdc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// QueryRecordsByBond - get all records for the given bond.
|
|
|
|
func (k RecordKeeper) QueryRecordsByBond(ctx sdk.Context, bondID string) []types.Record {
|
|
|
|
var records []types.Record
|
|
|
|
|
2022-10-17 07:15:01 +00:00
|
|
|
bondIDPrefix := append(PrefixBondIDToRecordsIndex, []byte(bondID)...) //nolint: all
|
2022-04-05 07:09:27 +00:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
itr := sdk.KVStorePrefixIterator(store, bondIDPrefix)
|
|
|
|
defer itr.Close()
|
|
|
|
for ; itr.Valid(); itr.Next() {
|
|
|
|
cid := itr.Key()[len(bondIDPrefix):]
|
|
|
|
bz := store.Get(append(PrefixCIDToRecordIndex, cid...))
|
|
|
|
if bz != nil {
|
2024-01-15 04:20:38 +00:00
|
|
|
var record types.Record
|
|
|
|
k.cdc.MustUnmarshal(bz, &record)
|
|
|
|
decodeRecordNames(store, &record)
|
|
|
|
records = append(records, record)
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return records
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessRenewRecord renews a record.
|
|
|
|
func (k Keeper) ProcessRenewRecord(ctx sdk.Context, msg types.MsgRenewRecord) error {
|
|
|
|
if !k.HasRecord(ctx, msg.RecordId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if renewal is required (i.e. expired record marked as deleted).
|
|
|
|
record := k.GetRecord(ctx, msg.RecordId)
|
2022-04-08 07:33:58 +00:00
|
|
|
expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime)
|
2022-04-14 03:56:12 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !record.Deleted || expiryTime.After(ctx.BlockTime()) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Renewal not required.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
2024-01-15 04:20:38 +00:00
|
|
|
recordType := record.ToReadableRecord()
|
2022-04-08 07:33:58 +00:00
|
|
|
err = k.processRecord(ctx, &recordType, true)
|
2022-04-05 07:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessAssociateBond associates a record with a bond.
|
|
|
|
func (k Keeper) ProcessAssociateBond(ctx sdk.Context, msg types.MsgAssociateBond) error {
|
|
|
|
if !k.HasRecord(ctx, msg.RecordId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !k.bondKeeper.HasBond(ctx, msg.BondId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if already associated with a bond.
|
|
|
|
record := k.GetRecord(ctx, msg.RecordId)
|
|
|
|
if record.BondId != "" || len(record.BondId) != 0 {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond already exists.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only the bond owner can associate a record with the bond.
|
|
|
|
bond := k.bondKeeper.GetBond(ctx, msg.BondId)
|
|
|
|
if msg.Signer != bond.Owner {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
record.BondId = msg.BondId
|
|
|
|
k.PutRecord(ctx, record)
|
|
|
|
k.AddBondToRecordIndexEntry(ctx, msg.BondId, msg.RecordId)
|
|
|
|
|
|
|
|
// Required so that renewal is triggered (with new bond ID) for expired records.
|
|
|
|
if record.Deleted {
|
|
|
|
k.InsertRecordExpiryQueue(ctx, record)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessDissociateBond dissociates a record from its bond.
|
|
|
|
func (k Keeper) ProcessDissociateBond(ctx sdk.Context, msg types.MsgDissociateBond) error {
|
|
|
|
if !k.HasRecord(ctx, msg.RecordId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Record not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if associated with a bond.
|
|
|
|
record := k.GetRecord(ctx, msg.RecordId)
|
|
|
|
bondID := record.BondId
|
|
|
|
if bondID == "" || len(bondID) == 0 {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only the bond owner can dissociate a record from the bond.
|
|
|
|
bond := k.bondKeeper.GetBond(ctx, bondID)
|
|
|
|
if msg.Signer != bond.Owner {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear bond ID.
|
|
|
|
record.BondId = ""
|
|
|
|
k.PutRecord(ctx, record)
|
|
|
|
k.RemoveBondToRecordIndexEntry(ctx, bondID, record.Id)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessDissociateRecords dissociates all records associated with a given bond.
|
|
|
|
func (k Keeper) ProcessDissociateRecords(ctx sdk.Context, msg types.MsgDissociateRecords) error {
|
|
|
|
if !k.bondKeeper.HasBond(ctx, msg.BondId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only the bond owner can dissociate all records from the bond.
|
|
|
|
bond := k.bondKeeper.GetBond(ctx, msg.BondId)
|
|
|
|
if msg.Signer != bond.Owner {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dissociate all records from the bond.
|
|
|
|
records := k.recordKeeper.QueryRecordsByBond(ctx, msg.BondId)
|
|
|
|
for _, record := range records {
|
|
|
|
// Clear bond ID.
|
|
|
|
record.BondId = ""
|
|
|
|
k.PutRecord(ctx, record)
|
|
|
|
k.RemoveBondToRecordIndexEntry(ctx, msg.BondId, record.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessReAssociateRecords switches records from and old to new bond.
|
|
|
|
func (k Keeper) ProcessReAssociateRecords(ctx sdk.Context, msg types.MsgReAssociateRecords) error {
|
|
|
|
if !k.bondKeeper.HasBond(ctx, msg.OldBondId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Old bond not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !k.bondKeeper.HasBond(ctx, msg.NewBondId) {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "New bond not found.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only the bond owner can re-associate all records.
|
|
|
|
oldBond := k.bondKeeper.GetBond(ctx, msg.OldBondId)
|
|
|
|
if msg.Signer != oldBond.Owner {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Old bond owner mismatch.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
newBond := k.bondKeeper.GetBond(ctx, msg.NewBondId)
|
|
|
|
if msg.Signer != newBond.Owner {
|
2023-03-15 15:22:35 +00:00
|
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "New bond owner mismatch.")
|
2022-04-05 07:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Re-associate all records.
|
|
|
|
records := k.recordKeeper.QueryRecordsByBond(ctx, msg.OldBondId)
|
|
|
|
for _, record := range records {
|
|
|
|
// Switch bond ID.
|
|
|
|
record.BondId = msg.NewBondId
|
|
|
|
k.PutRecord(ctx, record)
|
|
|
|
|
|
|
|
k.RemoveBondToRecordIndexEntry(ctx, msg.OldBondId, record.Id)
|
|
|
|
k.AddBondToRecordIndexEntry(ctx, msg.NewBondId, record.Id)
|
|
|
|
|
|
|
|
// Required so that renewal is triggered (with new bond ID) for expired records.
|
|
|
|
if record.Deleted {
|
|
|
|
k.InsertRecordExpiryQueue(ctx, record)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|