Additional registry module commands (#6)
For registry module: - Add commands to: - get records by bond id - reserve name authority - get name authority - set authority bond id - set name - get and list names - resolve name to a record - delete name - Handle: - genesis import / export - returning names when fetching record(s) - sub-authority reservation - To be handled in an upcoming PR: - module end blocker - record expiry - command to renew records - bond-association commands Reviewed-on: deep-stack/laconic2d#6 Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com> Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
This commit is contained in:
parent
5e68c7d9b3
commit
92764535a6
@ -217,17 +217,17 @@ func (k Keeper) ListAuctions(ctx sdk.Context) ([]auctiontypes.Auction, error) {
|
|||||||
func (k Keeper) MatchAuctions(ctx sdk.Context, matchFn func(*auctiontypes.Auction) (bool, error)) ([]*auctiontypes.Auction, error) {
|
func (k Keeper) MatchAuctions(ctx sdk.Context, matchFn func(*auctiontypes.Auction) (bool, error)) ([]*auctiontypes.Auction, error) {
|
||||||
var auctions []*auctiontypes.Auction
|
var auctions []*auctiontypes.Auction
|
||||||
|
|
||||||
err := k.Auctions.Walk(ctx, nil, func(key string, value auctiontypes.Auction) (stop bool, err error) {
|
err := k.Auctions.Walk(ctx, nil, func(key string, value auctiontypes.Auction) (bool, error) {
|
||||||
auctionMatched, err := matchFn(&value)
|
auctionMatched, err := matchFn(&value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if auctionMatched {
|
if auctionMatched {
|
||||||
auctions = append(auctions, &value)
|
auctions = append(auctions, &value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -532,10 +532,8 @@ func (k Keeper) GetAuctionModuleBalances(ctx sdk.Context) sdk.Coins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) EndBlockerProcessAuctions(ctx sdk.Context) error {
|
func (k Keeper) EndBlockerProcessAuctions(ctx sdk.Context) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
// Transition auction state (commit, reveal, expired, completed).
|
// Transition auction state (commit, reveal, expired, completed).
|
||||||
if err = k.processAuctionPhases(ctx); err != nil {
|
if err := k.processAuctionPhases(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,23 @@ import (
|
|||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ sdk.Msg = &MsgCommitBid{}
|
var (
|
||||||
|
_ sdk.Msg = &MsgCreateAuction{}
|
||||||
|
_ sdk.Msg = &MsgCommitBid{}
|
||||||
|
_ sdk.Msg = &MsgRevealBid{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMsgCreateAuction is the constructor function for MsgCreateAuction.
|
||||||
|
func NewMsgCreateAuction(params Params, signer sdk.AccAddress) MsgCreateAuction {
|
||||||
|
return MsgCreateAuction{
|
||||||
|
CommitsDuration: params.CommitsDuration,
|
||||||
|
RevealsDuration: params.RevealsDuration,
|
||||||
|
CommitFee: params.CommitFee,
|
||||||
|
RevealFee: params.RevealFee,
|
||||||
|
MinimumBid: params.MinimumBid,
|
||||||
|
Signer: signer.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewMsgCommitBid is the constructor function for MsgCommitBid.
|
// NewMsgCommitBid is the constructor function for MsgCommitBid.
|
||||||
func NewMsgCommitBid(auctionID string, commitHash string, signer sdk.AccAddress) MsgCommitBid {
|
func NewMsgCommitBid(auctionID string, commitHash string, signer sdk.AccAddress) MsgCommitBid {
|
||||||
|
@ -1,80 +1,99 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"git.vdb.to/cerc-io/laconic2d/x/registry"
|
"git.vdb.to/cerc-io/laconic2d/x/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitGenesis initializes the module state from a genesis state.
|
// InitGenesis initializes the module state from a genesis state.
|
||||||
func (k *Keeper) InitGenesis(ctx context.Context, data *registry.GenesisState) error {
|
func (k *Keeper) InitGenesis(ctx sdk.Context, data *registry.GenesisState) error {
|
||||||
if err := k.Params.Set(ctx, data.Params); err != nil {
|
if err := k.Params.Set(ctx, data.Params); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// for _, record := range data.Records {
|
for _, record := range data.Records {
|
||||||
// keeper.PutRecord(ctx, record)
|
if err := k.SaveRecord(ctx, record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// // Add to record expiry queue if expiry time is in the future.
|
// Add to record expiry queue if expiry time is in the future.
|
||||||
// expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime)
|
expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// panic(err)
|
panic(err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if expiryTime.After(ctx.BlockTime()) {
|
if expiryTime.After(ctx.BlockTime()) {
|
||||||
// keeper.InsertRecordExpiryQueue(ctx, record)
|
// TODO
|
||||||
// }
|
// k.InsertRecordExpiryQueue(ctx, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // Note: Bond genesis runs first, so bonds will already be present.
|
for _, authority := range data.Authorities {
|
||||||
// if record.BondId != "" {
|
// Only import authorities that are marked active.
|
||||||
// keeper.AddBondToRecordIndexEntry(ctx, record.BondId, record.Id)
|
if authority.Entry.Status == registry.AuthorityActive {
|
||||||
// }
|
if err := k.SaveNameAuthority(ctx, authority.Name, authority.Entry); err != nil {
|
||||||
// }
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// for _, authority := range data.Authorities {
|
// TODO
|
||||||
// // Only import authorities that are marked active.
|
// Add authority name to expiry queue.
|
||||||
// if authority.Entry.Status == types.AuthorityActive {
|
// k.InsertAuthorityExpiryQueue(ctx, authority.Name, authority.Entry.ExpiryTime)
|
||||||
// keeper.SetNameAuthority(ctx, authority.Name, authority.Entry)
|
|
||||||
|
|
||||||
// // Add authority name to expiry queue.
|
// TODO
|
||||||
// keeper.InsertAuthorityExpiryQueue(ctx, authority.Name, authority.Entry.ExpiryTime)
|
// Note: Bond genesis runs first, so bonds will already be present.
|
||||||
|
// if authority.Entry.BondId != "" {
|
||||||
|
// k.AddBondToAuthorityIndexEntry(ctx, authority.Entry.BondId, authority.Name)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // Note: Bond genesis runs first, so bonds will already be present.
|
for _, nameEntry := range data.Names {
|
||||||
// if authority.Entry.BondId != "" {
|
if err := k.SaveNameRecord(ctx, nameEntry.Name, nameEntry.Entry.Latest.Id); err != nil {
|
||||||
// keeper.AddBondToAuthorityIndexEntry(ctx, authority.Entry.BondId, authority.Name)
|
return err
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// for _, nameEntry := range data.Names {
|
|
||||||
// keeper.SetNameRecord(ctx, nameEntry.Name, nameEntry.Entry.Latest.Id)
|
|
||||||
// }
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportGenesis exports the module state to a genesis state.
|
// ExportGenesis exports the module state to a genesis state.
|
||||||
func (k *Keeper) ExportGenesis(ctx context.Context) (*registry.GenesisState, error) {
|
func (k *Keeper) ExportGenesis(ctx sdk.Context) (*registry.GenesisState, error) {
|
||||||
params, err := k.Params.Get(ctx)
|
params, err := k.Params.Get(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// records := keeper.ListRecords(ctx)
|
records, err := k.ListRecords(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// authorities := keeper.ListNameAuthorityRecords(ctx)
|
authorities, err := k.ListNameAuthorityRecords(ctx)
|
||||||
// authorityEntries := []types.AuthorityEntry{}
|
if err != nil {
|
||||||
// // #nosec G705
|
return nil, err
|
||||||
// for name, record := range authorities {
|
}
|
||||||
// authorityEntries = append(authorityEntries, types.AuthorityEntry{
|
|
||||||
// Name: name,
|
|
||||||
// Entry: &record, //nolint: all
|
|
||||||
// }) // #nosec G601
|
|
||||||
// }
|
|
||||||
|
|
||||||
// names := keeper.ListNameRecords(ctx)
|
authorityEntries := []registry.AuthorityEntry{}
|
||||||
|
// #nosec G705
|
||||||
|
for name, record := range authorities {
|
||||||
|
authorityEntries = append(authorityEntries, registry.AuthorityEntry{
|
||||||
|
Name: name,
|
||||||
|
Entry: &record, //nolint: all
|
||||||
|
}) // #nosec G601
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := k.ListNameRecords(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return ®istry.GenesisState{
|
return ®istry.GenesisState{
|
||||||
Params: params,
|
Params: params,
|
||||||
|
Records: records,
|
||||||
|
Authorities: authorityEntries,
|
||||||
|
Names: names,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func (b RecordsIndexes) IndexesList() []collections.Index[string, registrytypes.
|
|||||||
func newRecordIndexes(sb *collections.SchemaBuilder) RecordsIndexes {
|
func newRecordIndexes(sb *collections.SchemaBuilder) RecordsIndexes {
|
||||||
return RecordsIndexes{
|
return RecordsIndexes{
|
||||||
BondId: indexes.NewMulti(
|
BondId: indexes.NewMulti(
|
||||||
sb, registrytypes.BondIdIndexPrefix, "records_by_bond_id",
|
sb, registrytypes.RecordsByBondIdIndexPrefix, "records_by_bond_id",
|
||||||
collections.StringKey, collections.StringKey,
|
collections.StringKey, collections.StringKey,
|
||||||
func(_ string, v registrytypes.Record) (string, error) {
|
func(_ string, v registrytypes.Record) (string, error) {
|
||||||
return v.BondId, nil
|
return v.BondId, nil
|
||||||
@ -49,6 +49,38 @@ func newRecordIndexes(sb *collections.SchemaBuilder) RecordsIndexes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
type AuthoritiesIndexes struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b AuthoritiesIndexes) IndexesList() []collections.Index[string, registrytypes.NameAuthority] {
|
||||||
|
return []collections.Index[string, registrytypes.NameAuthority]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
type Keeper struct {
|
||||||
cdc codec.BinaryCodec
|
cdc codec.BinaryCodec
|
||||||
|
|
||||||
@ -59,9 +91,11 @@ type Keeper struct {
|
|||||||
auctionKeeper auctionkeeper.Keeper
|
auctionKeeper auctionkeeper.Keeper
|
||||||
|
|
||||||
// state management
|
// state management
|
||||||
Schema collections.Schema
|
Schema collections.Schema
|
||||||
Params collections.Item[registrytypes.Params]
|
Params collections.Item[registrytypes.Params]
|
||||||
Records *collections.IndexedMap[string, registrytypes.Record, RecordsIndexes]
|
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
|
// NewKeeper creates a new Keeper instance
|
||||||
@ -83,7 +117,21 @@ func NewKeeper(
|
|||||||
bondKeeper: bondKeeper,
|
bondKeeper: bondKeeper,
|
||||||
auctionKeeper: auctionKeeper,
|
auctionKeeper: auctionKeeper,
|
||||||
Params: collections.NewItem(sb, registrytypes.ParamsPrefix, "params", codec.CollValue[registrytypes.Params](cdc)),
|
Params: collections.NewItem(sb, registrytypes.ParamsPrefix, "params", codec.CollValue[registrytypes.Params](cdc)),
|
||||||
Records: collections.NewIndexedMap(sb, registrytypes.RecordsPrefix, "records", collections.StringKey, codec.CollValue[registrytypes.Record](cdc), newRecordIndexes(sb)),
|
Records: collections.NewIndexedMap(
|
||||||
|
sb, registrytypes.RecordsPrefix, "records",
|
||||||
|
collections.StringKey, codec.CollValue[registrytypes.Record](cdc),
|
||||||
|
newRecordIndexes(sb),
|
||||||
|
),
|
||||||
|
Authorities: collections.NewIndexedMap(
|
||||||
|
sb, registrytypes.AuthoritiesPrefix, "authorities",
|
||||||
|
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()
|
schema, err := sb.Build()
|
||||||
@ -106,27 +154,61 @@ func (k Keeper) HasRecord(ctx sdk.Context, id string) (bool, error) {
|
|||||||
return has, nil
|
return has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecord - gets a record from the store.
|
// ListRecords - get all records.
|
||||||
func (k Keeper) GetRecord(ctx sdk.Context, id string) (registrytypes.Record, error) {
|
func (k Keeper) ListRecords(ctx sdk.Context) ([]registrytypes.Record, error) {
|
||||||
|
var records []registrytypes.Record
|
||||||
|
|
||||||
|
err := k.Records.Walk(ctx, nil, func(key string, value registrytypes.Record) (bool, error) {
|
||||||
|
if err := k.populateRecordNames(ctx, &value); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
records = append(records, value)
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecordById - gets a record from the store.
|
||||||
|
func (k Keeper) GetRecordById(ctx sdk.Context, id string) (registrytypes.Record, error) {
|
||||||
record, err := k.Records.Get(ctx, id)
|
record, err := k.Records.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.Record{}, err
|
return registrytypes.Record{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := k.populateRecordNames(ctx, &record); err != nil {
|
||||||
|
return registrytypes.Record{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRecords - get all records.
|
// GetRecordsByBondId - gets a record from the store.
|
||||||
func (k Keeper) ListRecords(ctx sdk.Context) ([]registrytypes.Record, error) {
|
func (k Keeper) GetRecordsByBondId(ctx sdk.Context, bondId string) ([]registrytypes.Record, error) {
|
||||||
iter, err := k.Records.Iterate(ctx, nil)
|
var records []registrytypes.Record
|
||||||
|
|
||||||
|
err := k.Records.Indexes.BondId.Walk(ctx, collections.NewPrefixedPairRange[string, string](bondId), func(bondId string, id string) (bool, error) {
|
||||||
|
record, err := k.Records.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := k.populateRecordNames(ctx, &record); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
records = append(records, record)
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return []registrytypes.Record{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check if required
|
return records, nil
|
||||||
// decodeRecordNames(store, &record)
|
|
||||||
|
|
||||||
return iter.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordsFromAttributes gets a list of records whose attributes match all provided values
|
// RecordsFromAttributes gets a list of records whose attributes match all provided values
|
||||||
@ -292,7 +374,41 @@ func (k Keeper) processAttributeMap(ctx sdk.Context, n ipld.Node, id string, pre
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k Keeper) populateRecordNames(ctx sdk.Context, record *registrytypes.Record) error {
|
||||||
|
iter, err := k.NameRecords.Indexes.Cid.MatchExact(ctx, record.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := iter.PrimaryKeys()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record.Names = names
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetModuleBalances gets the registry module account(s) balances.
|
// GetModuleBalances gets the registry module account(s) balances.
|
||||||
func (k Keeper) GetModuleBalances(ctx sdk.Context) []*registrytypes.AccountBalance {
|
func (k Keeper) GetModuleBalances(ctx sdk.Context) []*registrytypes.AccountBalance {
|
||||||
panic("unimplemented")
|
var balances []*registrytypes.AccountBalance
|
||||||
|
accountNames := []string{
|
||||||
|
registrytypes.RecordRentModuleAccountName,
|
||||||
|
registrytypes.AuthorityRentModuleAccountName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, accountName := range accountNames {
|
||||||
|
moduleAddress := k.accountKeeper.GetModuleAddress(accountName)
|
||||||
|
|
||||||
|
moduleAccount := k.accountKeeper.GetAccount(ctx, moduleAddress)
|
||||||
|
if moduleAccount != nil {
|
||||||
|
accountBalance := k.bankKeeper.GetAllBalances(ctx, moduleAddress)
|
||||||
|
balances = append(balances, ®istrytypes.AccountBalance{
|
||||||
|
AccountName: accountName,
|
||||||
|
Balance: accountBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return balances
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ func NewMsgServerImpl(keeper Keeper) registrytypes.MsgServer {
|
|||||||
|
|
||||||
func (ms msgServer) SetRecord(c context.Context, msg *registrytypes.MsgSetRecord) (*registrytypes.MsgSetRecordResponse, error) {
|
func (ms msgServer) SetRecord(c context.Context, msg *registrytypes.MsgSetRecord) (*registrytypes.MsgSetRecordResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -51,14 +52,17 @@ func (ms msgServer) SetRecord(c context.Context, msg *registrytypes.MsgSetRecord
|
|||||||
// nolint: all
|
// nolint: all
|
||||||
func (ms msgServer) SetName(c context.Context, msg *registrytypes.MsgSetName) (*registrytypes.MsgSetNameResponse, error) {
|
func (ms msgServer) SetName(c context.Context, msg *registrytypes.MsgSetName) (*registrytypes.MsgSetNameResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = ms.k.ProcessSetName(ctx, *msg)
|
|
||||||
|
err = ms.k.SetName(ctx, *msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.EventManager().EmitEvents(sdk.Events{
|
ctx.EventManager().EmitEvents(sdk.Events{
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
registrytypes.EventTypeSetRecord,
|
registrytypes.EventTypeSetRecord,
|
||||||
@ -77,6 +81,7 @@ func (ms msgServer) SetName(c context.Context, msg *registrytypes.MsgSetName) (*
|
|||||||
|
|
||||||
func (ms msgServer) ReserveName(c context.Context, msg *registrytypes.MsgReserveAuthority) (*registrytypes.MsgReserveAuthorityResponse, error) {
|
func (ms msgServer) ReserveName(c context.Context, msg *registrytypes.MsgReserveAuthority) (*registrytypes.MsgReserveAuthorityResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,10 +90,12 @@ func (ms msgServer) ReserveName(c context.Context, msg *registrytypes.MsgReserve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = ms.k.ProcessReserveAuthority(ctx, *msg)
|
|
||||||
|
err = ms.k.ReserveAuthority(ctx, *msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.EventManager().EmitEvents(sdk.Events{
|
ctx.EventManager().EmitEvents(sdk.Events{
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
registrytypes.EventTypeReserveNameAuthority,
|
registrytypes.EventTypeReserveNameAuthority,
|
||||||
@ -108,11 +115,12 @@ func (ms msgServer) ReserveName(c context.Context, msg *registrytypes.MsgReserve
|
|||||||
// nolint: all
|
// nolint: all
|
||||||
func (ms msgServer) SetAuthorityBond(c context.Context, msg *registrytypes.MsgSetAuthorityBond) (*registrytypes.MsgSetAuthorityBondResponse, error) {
|
func (ms msgServer) SetAuthorityBond(c context.Context, msg *registrytypes.MsgSetAuthorityBond) (*registrytypes.MsgSetAuthorityBondResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = ms.k.ProcessSetAuthorityBond(ctx, *msg)
|
err = ms.k.SetAuthorityBond(ctx, *msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -134,14 +142,16 @@ func (ms msgServer) SetAuthorityBond(c context.Context, msg *registrytypes.MsgSe
|
|||||||
|
|
||||||
func (ms msgServer) DeleteName(c context.Context, msg *registrytypes.MsgDeleteNameAuthority) (*registrytypes.MsgDeleteNameAuthorityResponse, error) {
|
func (ms msgServer) DeleteName(c context.Context, msg *registrytypes.MsgDeleteNameAuthority) (*registrytypes.MsgDeleteNameAuthorityResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
_, err := sdk.AccAddressFromBech32(msg.Signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = ms.k.ProcessDeleteName(ctx, *msg)
|
err = ms.k.DeleteName(ctx, *msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.EventManager().EmitEvents(sdk.Events{
|
ctx.EventManager().EmitEvents(sdk.Events{
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
registrytypes.EventTypeDeleteName,
|
registrytypes.EventTypeDeleteName,
|
||||||
|
@ -1,48 +1,409 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
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.
|
// GetNameAuthority - gets a name authority from the store.
|
||||||
func (k Keeper) GetNameAuthority(ctx sdk.Context, name string) registrytypes.NameAuthority {
|
func (k Keeper) GetNameAuthority(ctx sdk.Context, name string) (registrytypes.NameAuthority, error) {
|
||||||
panic("unimplemented")
|
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.
|
// HasNameRecord - checks if a name record exists.
|
||||||
func (k Keeper) HasNameRecord(ctx sdk.Context, crn string) bool {
|
func (k Keeper) HasNameRecord(ctx sdk.Context, crn string) (bool, error) {
|
||||||
panic("unimplemented")
|
return k.NameRecords.Has(ctx, crn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNameRecord - gets a name record from the store.
|
// GetNameRecord - gets a name record from the store.
|
||||||
func (k Keeper) GetNameRecord(ctx sdk.Context, crn string) *registrytypes.NameRecord {
|
func (k Keeper) GetNameRecord(ctx sdk.Context, crn string) (*registrytypes.NameRecord, error) {
|
||||||
panic("unimplemented")
|
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.
|
// ListNameRecords - get all name records.
|
||||||
func (k Keeper) ListNameRecords(ctx sdk.Context) []registrytypes.NameEntry {
|
func (k Keeper) ListNameRecords(ctx sdk.Context) ([]registrytypes.NameEntry, error) {
|
||||||
panic("unimplemented")
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessSetName creates a CRN -> Record ID mapping.
|
// SaveNameRecord - sets a name record.
|
||||||
func (k Keeper) ProcessSetName(ctx sdk.Context, msg registrytypes.MsgSetName) error {
|
func (k Keeper) SaveNameRecord(ctx sdk.Context, crn string, id string) error {
|
||||||
panic("unimplemented")
|
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 = ®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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessReserveAuthority reserves a name authority.
|
// SetName creates a CRN -> Record ID mapping.
|
||||||
func (k Keeper) ProcessReserveAuthority(ctx sdk.Context, msg registrytypes.MsgReserveAuthority) error {
|
func (k Keeper) SetName(ctx sdk.Context, msg registrytypes.MsgSetName) error {
|
||||||
panic("unimplemented")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) ProcessSetAuthorityBond(ctx sdk.Context, msg registrytypes.MsgSetAuthorityBond) error {
|
// SaveNameAuthority creates the NameAuthority record.
|
||||||
panic("unimplemented")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessDeleteName removes a CRN -> Record ID mapping.
|
// ReserveAuthority reserves a name authority.
|
||||||
func (k Keeper) ProcessDeleteName(ctx sdk.Context, msg registrytypes.MsgDeleteNameAuthority) error {
|
func (k Keeper) ReserveAuthority(ctx sdk.Context, msg registrytypes.MsgReserveAuthority) error {
|
||||||
panic("unimplemented")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// k.InsertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) GetAuthorityExpiryQueue(ctx sdk.Context) []*registrytypes.ExpiryQueueRecord {
|
func (k Keeper) GetAuthorityExpiryQueue(ctx sdk.Context) []*registrytypes.ExpiryQueueRecord {
|
||||||
@ -50,6 +411,119 @@ func (k Keeper) GetAuthorityExpiryQueue(ctx sdk.Context) []*registrytypes.Expiry
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveCRN resolves a CRN to a record.
|
// ResolveCRN resolves a CRN to a record.
|
||||||
func (k Keeper) ResolveCRN(ctx sdk.Context, crn string) *registrytypes.Record {
|
func (k Keeper) ResolveCRN(ctx sdk.Context, crn string) (*registrytypes.Record, error) {
|
||||||
panic("unimplemented")
|
_, _, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthorityPubKey(pubKey cryptotypes.PubKey) string {
|
||||||
|
if pubKey == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return helpers.BytesToBase64(pubKey.Bytes())
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func (qs queryServer) GetRecord(c context.Context, req *registrytypes.QueryRecor
|
|||||||
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "Record not found.")
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "Record not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := qs.k.GetRecord(ctx, id)
|
record, err := qs.k.GetRecordById(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -77,7 +77,12 @@ func (qs queryServer) GetRecord(c context.Context, req *registrytypes.QueryRecor
|
|||||||
|
|
||||||
func (qs queryServer) GetRecordsByBondId(c context.Context, req *registrytypes.QueryRecordsByBondIdRequest) (*registrytypes.QueryRecordsByBondIdResponse, error) {
|
func (qs queryServer) GetRecordsByBondId(c context.Context, req *registrytypes.QueryRecordsByBondIdRequest) (*registrytypes.QueryRecordsByBondIdResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
records := qs.k.recordKeeper.QueryRecordsByBond(ctx, req.GetId())
|
|
||||||
|
records, err := qs.k.GetRecordsByBondId(ctx, req.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return ®istrytypes.QueryRecordsByBondIdResponse{Records: records}, nil
|
return ®istrytypes.QueryRecordsByBondIdResponse{Records: records}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +90,9 @@ func (qs queryServer) GetRegistryModuleBalance(c context.Context,
|
|||||||
_ *registrytypes.QueryGetRegistryModuleBalanceRequest,
|
_ *registrytypes.QueryGetRegistryModuleBalanceRequest,
|
||||||
) (*registrytypes.QueryGetRegistryModuleBalanceResponse, error) {
|
) (*registrytypes.QueryGetRegistryModuleBalanceResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
balances := qs.k.GetModuleBalances(ctx)
|
balances := qs.k.GetModuleBalances(ctx)
|
||||||
|
|
||||||
return ®istrytypes.QueryGetRegistryModuleBalanceResponse{
|
return ®istrytypes.QueryGetRegistryModuleBalanceResponse{
|
||||||
Balances: balances,
|
Balances: balances,
|
||||||
}, nil
|
}, nil
|
||||||
@ -93,36 +100,63 @@ func (qs queryServer) GetRegistryModuleBalance(c context.Context,
|
|||||||
|
|
||||||
func (qs queryServer) NameRecords(c context.Context, _ *registrytypes.QueryNameRecordsRequest) (*registrytypes.QueryNameRecordsResponse, error) {
|
func (qs queryServer) NameRecords(c context.Context, _ *registrytypes.QueryNameRecordsRequest) (*registrytypes.QueryNameRecordsResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
nameRecords := qs.k.ListNameRecords(ctx)
|
|
||||||
|
nameRecords, err := qs.k.ListNameRecords(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return ®istrytypes.QueryNameRecordsResponse{Names: nameRecords}, nil
|
return ®istrytypes.QueryNameRecordsResponse{Names: nameRecords}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs queryServer) Whois(c context.Context, request *registrytypes.QueryWhoisRequest) (*registrytypes.QueryWhoisResponse, error) {
|
func (qs queryServer) Whois(c context.Context, request *registrytypes.QueryWhoisRequest) (*registrytypes.QueryWhoisResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
nameAuthority := qs.k.GetNameAuthority(ctx, request.GetName())
|
|
||||||
|
nameAuthority, err := qs.k.GetNameAuthority(ctx, request.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return ®istrytypes.QueryWhoisResponse{NameAuthority: nameAuthority}, nil
|
return ®istrytypes.QueryWhoisResponse{NameAuthority: nameAuthority}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs queryServer) LookupCrn(c context.Context, req *registrytypes.QueryLookupCrnRequest) (*registrytypes.QueryLookupCrnResponse, error) {
|
func (qs queryServer) LookupCrn(c context.Context, req *registrytypes.QueryLookupCrnRequest) (*registrytypes.QueryLookupCrnResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
crn := req.GetCrn()
|
crn := req.GetCrn()
|
||||||
if !qs.k.HasNameRecord(ctx, crn) {
|
|
||||||
|
crnExists, err := qs.k.HasNameRecord(ctx, crn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !crnExists {
|
||||||
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "CRN not found.")
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "CRN not found.")
|
||||||
}
|
}
|
||||||
nameRecord := qs.k.GetNameRecord(ctx, crn)
|
|
||||||
|
nameRecord, err := qs.k.LookupNameRecord(ctx, crn)
|
||||||
if nameRecord == nil {
|
if nameRecord == nil {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "name record not found.")
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "name record not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ®istrytypes.QueryLookupCrnResponse{Name: nameRecord}, nil
|
return ®istrytypes.QueryLookupCrnResponse{Name: nameRecord}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs queryServer) ResolveCrn(c context.Context, req *registrytypes.QueryResolveCrnRequest) (*registrytypes.QueryResolveCrnResponse, error) {
|
func (qs queryServer) ResolveCrn(c context.Context, req *registrytypes.QueryResolveCrnRequest) (*registrytypes.QueryResolveCrnResponse, error) {
|
||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
crn := req.GetCrn()
|
crn := req.GetCrn()
|
||||||
record := qs.k.ResolveCRN(ctx, crn)
|
record, err := qs.k.ResolveCRN(ctx, crn)
|
||||||
if record == nil {
|
if record == nil {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "record not found.")
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "record not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ®istrytypes.QueryResolveCrnResponse{Record: record}, nil
|
return ®istrytypes.QueryResolveCrnResponse{Record: record}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,11 +17,6 @@ type RecordKeeper struct {
|
|||||||
// storeKey storetypes.StoreKey // Unexposed key to access store from sdk.Context
|
// storeKey storetypes.StoreKey // Unexposed key to access store from sdk.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRecordsByBond - get all records for the given bond.
|
|
||||||
func (k RecordKeeper) QueryRecordsByBond(ctx sdk.Context, bondID string) []registrytypes.Record {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessRenewRecord renews a record.
|
// ProcessRenewRecord renews a record.
|
||||||
func (k Keeper) ProcessRenewRecord(ctx sdk.Context, msg registrytypes.MsgRenewRecord) error {
|
func (k Keeper) ProcessRenewRecord(ctx sdk.Context, msg registrytypes.MsgRenewRecord) error {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
|
@ -18,6 +18,13 @@ var (
|
|||||||
// ParamsKey is the prefix for params key
|
// ParamsKey is the prefix for params key
|
||||||
ParamsPrefix = collections.NewPrefix(0)
|
ParamsPrefix = collections.NewPrefix(0)
|
||||||
|
|
||||||
RecordsPrefix = collections.NewPrefix(1)
|
RecordsPrefix = collections.NewPrefix(1)
|
||||||
BondIdIndexPrefix = collections.NewPrefix(2)
|
RecordsByBondIdIndexPrefix = collections.NewPrefix(2)
|
||||||
|
|
||||||
|
AuthoritiesPrefix = collections.NewPrefix(3)
|
||||||
|
AuthoritiesByAuctionIdIndexPrefix = collections.NewPrefix(4)
|
||||||
|
AuthoritiesByBondIdIndexPrefix = collections.NewPrefix(5)
|
||||||
|
|
||||||
|
NameRecordsPrefix = collections.NewPrefix(6)
|
||||||
|
NameRecordsByCidIndexPrefix = collections.NewPrefix(7)
|
||||||
)
|
)
|
||||||
|
@ -35,11 +35,91 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
|
|||||||
{ProtoField: "id"},
|
{ProtoField: "id"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "GetRecordsByBondId",
|
||||||
|
Use: "get-records-by-bond-id [bond-id]",
|
||||||
|
Short: "Get records by bond id",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "id"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "Whois",
|
||||||
|
Use: "whois [name]",
|
||||||
|
Short: "Get name authority info",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "NameRecords",
|
||||||
|
Use: "names",
|
||||||
|
Short: "List name records",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "LookupCrn",
|
||||||
|
Use: "lookup [crn]",
|
||||||
|
Short: "Get naming info for CRN",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "crn"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "ResolveCrn",
|
||||||
|
Use: "resolve [crn]",
|
||||||
|
Short: "Resolve CRN to record",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "crn"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "GetRegistryModuleBalance",
|
||||||
|
Use: "balance",
|
||||||
|
Short: "Get registry module account balances",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tx: &autocliv1.ServiceCommandDescriptor{
|
Tx: &autocliv1.ServiceCommandDescriptor{
|
||||||
Service: registryv1.Msg_ServiceDesc.ServiceName,
|
Service: registryv1.Msg_ServiceDesc.ServiceName,
|
||||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{},
|
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||||
|
{
|
||||||
|
RpcMethod: "ReserveName",
|
||||||
|
Use: "reserve-name [name] [owner]",
|
||||||
|
Short: "Reserve name",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "name"},
|
||||||
|
{ProtoField: "owner"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "SetAuthorityBond",
|
||||||
|
Use: "authority-bond [name] [bond-id]",
|
||||||
|
Short: "Associate authority with bond",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "name"},
|
||||||
|
{ProtoField: "bond_id"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "SetName",
|
||||||
|
Use: "set-name [crn] [cid]",
|
||||||
|
Short: "Set CRN to CID mapping",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "crn"},
|
||||||
|
{ProtoField: "cid"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RpcMethod: "DeleteName",
|
||||||
|
Use: "delete-name [crn]",
|
||||||
|
Short: "Delete CRN",
|
||||||
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||||
|
{ProtoField: "crn"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
EnhanceCustomCommand: true, // Allow additional manual commands
|
EnhanceCustomCommand: true, // Allow additional manual commands
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user