diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index fbe65b01..19b1608c 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -61,6 +61,26 @@ func newAuthorityIndexes(sb *collections.SchemaBuilder) AuthoritiesIndexes { return AuthoritiesIndexes{} } +type NameRecordsIndexes struct { + Cid *indexes.Multi[string, string, registrytypes.NameRecord] +} + +func (b NameRecordsIndexes) IndexesList() []collections.Index[string, registrytypes.NameRecord] { + return []collections.Index[string, registrytypes.NameRecord]{b.Cid} +} + +func newNameRecordIndexes(sb *collections.SchemaBuilder) NameRecordsIndexes { + return NameRecordsIndexes{ + Cid: indexes.NewMulti( + sb, registrytypes.NameRecordsByCidIndexPrefix, "name_records_by_cid", + collections.StringKey, collections.StringKey, + func(_ string, v registrytypes.NameRecord) (string, error) { + return v.Latest.Id, nil + }, + ), + } +} + type Keeper struct { cdc codec.BinaryCodec @@ -75,6 +95,7 @@ type Keeper struct { 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] } // NewKeeper creates a new Keeper instance @@ -106,6 +127,11 @@ func NewKeeper( collections.StringKey, codec.CollValue[registrytypes.NameAuthority](cdc), newAuthorityIndexes(sb), ), + NameRecords: collections.NewIndexedMap( + sb, registrytypes.NameRecordsPrefix, "name_records", + collections.StringKey, codec.CollValue[registrytypes.NameRecord](cdc), + newNameRecordIndexes(sb), + ), } schema, err := sb.Build() diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 9cd61378..018d916c 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -51,14 +51,17 @@ func (ms msgServer) SetRecord(c context.Context, msg *registrytypes.MsgSetRecord // nolint: all func (ms msgServer) SetName(c context.Context, msg *registrytypes.MsgSetName) (*registrytypes.MsgSetNameResponse, error) { ctx := sdk.UnwrapSDKContext(c) + _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return nil, err } - err = ms.k.ProcessSetName(ctx, *msg) + + err = ms.k.SetName(ctx, *msg) if err != nil { return nil, err } + ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( registrytypes.EventTypeSetRecord, diff --git a/x/registry/keeper/naming_keeper.go b/x/registry/keeper/naming_keeper.go index 8c2eaba8..51e8ce7a 100644 --- a/x/registry/keeper/naming_keeper.go +++ b/x/registry/keeper/naming_keeper.go @@ -45,8 +45,40 @@ func (k Keeper) HasNameRecord(ctx sdk.Context, crn string) bool { } // GetNameRecord - gets a name record from the store. -func (k Keeper) GetNameRecord(ctx sdk.Context, crn string) *registrytypes.NameRecord { - panic("unimplemented") +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. @@ -54,9 +86,56 @@ func (k Keeper) ListNameRecords(ctx sdk.Context) []registrytypes.NameEntry { panic("unimplemented") } -// ProcessSetName creates a CRN -> Record ID mapping. -func (k Keeper) ProcessSetName(ctx sdk.Context, msg registrytypes.MsgSetName) error { - panic("unimplemented") +// SaveNameRecord - sets a name record. +func (k Keeper) SaveNameRecord(ctx sdk.Context, crn string, id string) error { + var nameRecord registrytypes.NameRecord + x, err := k.GetNameRecord(ctx, crn) + if err != nil { + return err + } + + if x != nil { + nameRecord = *x + nameRecord.History = append(nameRecord.History, nameRecord.Latest) + } + + nameRecord.Latest = ®istrytypes.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. @@ -253,6 +332,68 @@ func (k Keeper) ResolveCRN(ctx sdk.Context, crn string) *registrytypes.Record { panic("unimplemented") } +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 +} + func getAuthorityPubKey(pubKey cryptotypes.PubKey) string { if pubKey == nil { return "" diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index f125e196..ab6999d4 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -121,7 +121,7 @@ func (qs queryServer) LookupCrn(c context.Context, req *registrytypes.QueryLooku if !qs.k.HasNameRecord(ctx, crn) { return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "CRN not found.") } - nameRecord := qs.k.GetNameRecord(ctx, crn) + nameRecord, _ := qs.k.LookupNameRecord(ctx, crn) if nameRecord == nil { return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "name record not found.") } diff --git a/x/registry/keys.go b/x/registry/keys.go index 0f6c57cd..4063e74a 100644 --- a/x/registry/keys.go +++ b/x/registry/keys.go @@ -24,4 +24,7 @@ var ( AuthoritiesPrefix = collections.NewPrefix(3) AuthoritiesByAuctionIdIndexPrefix = collections.NewPrefix(4) AuthoritiesByBondIdIndexPrefix = collections.NewPrefix(5) + + NameRecordsPrefix = collections.NewPrefix(6) + NameRecordsByCidIndexPrefix = collections.NewPrefix(7) ) diff --git a/x/registry/module/autocli.go b/x/registry/module/autocli.go index 44fb09cf..177ed29d 100644 --- a/x/registry/module/autocli.go +++ b/x/registry/module/autocli.go @@ -80,6 +80,15 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { {ProtoField: "bond_id"}, }, }, + { + RpcMethod: "SetName", + Use: "set-name [crn] [cid]", + Short: "Set CRN to CID mapping", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{ + {ProtoField: "crn"}, + {ProtoField: "cid"}, + }, + }, }, EnhanceCustomCommand: true, // Allow additional manual commands },