2024-02-15 07:08:32 +00:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
2024-02-16 06:40:42 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
2024-02-15 07:08:32 +00:00
|
|
|
"cosmossdk.io/collections"
|
2024-02-16 06:40:42 +00:00
|
|
|
"cosmossdk.io/collections/indexes"
|
2024-02-15 07:08:32 +00:00
|
|
|
storetypes "cosmossdk.io/core/store"
|
2024-02-16 06:40:42 +00:00
|
|
|
errorsmod "cosmossdk.io/errors"
|
2024-02-15 07:08:32 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
2024-02-16 06:40:42 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2024-02-15 07:08:32 +00:00
|
|
|
auth "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
|
|
bank "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
2024-02-16 06:40:42 +00:00
|
|
|
"github.com/gibson042/canonicaljson-go"
|
|
|
|
"github.com/ipld/go-ipld-prime"
|
|
|
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
|
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
2024-02-15 07:08:32 +00:00
|
|
|
|
|
|
|
auctionkeeper "git.vdb.to/cerc-io/laconic2d/x/auction/keeper"
|
|
|
|
bondkeeper "git.vdb.to/cerc-io/laconic2d/x/bond/keeper"
|
|
|
|
registrytypes "git.vdb.to/cerc-io/laconic2d/x/registry"
|
2024-02-16 06:40:42 +00:00
|
|
|
"git.vdb.to/cerc-io/laconic2d/x/registry/helpers"
|
2024-02-15 07:08:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// TODO: Add required methods
|
|
|
|
|
2024-02-16 06:40:42 +00:00
|
|
|
type RecordsIndexes struct {
|
|
|
|
BondId *indexes.Multi[string, string, registrytypes.Record]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b RecordsIndexes) IndexesList() []collections.Index[string, registrytypes.Record] {
|
|
|
|
return []collections.Index[string, registrytypes.Record]{b.BondId}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRecordIndexes(sb *collections.SchemaBuilder) RecordsIndexes {
|
|
|
|
return RecordsIndexes{
|
|
|
|
BondId: indexes.NewMulti(
|
2024-02-16 09:32:52 +00:00
|
|
|
sb, registrytypes.RecordsByBondIdIndexPrefix, "records_by_bond_id",
|
2024-02-16 06:40:42 +00:00
|
|
|
collections.StringKey, collections.StringKey,
|
|
|
|
func(_ string, v registrytypes.Record) (string, error) {
|
|
|
|
return v.BondId, nil
|
|
|
|
},
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-16 09:32:52 +00:00
|
|
|
// 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{}
|
|
|
|
}
|
|
|
|
|
2024-02-15 07:08:32 +00:00
|
|
|
type Keeper struct {
|
|
|
|
cdc codec.BinaryCodec
|
|
|
|
|
|
|
|
accountKeeper auth.AccountKeeper
|
|
|
|
bankKeeper bank.Keeper
|
|
|
|
recordKeeper RecordKeeper
|
|
|
|
bondKeeper bondkeeper.Keeper
|
|
|
|
auctionKeeper auctionkeeper.Keeper
|
|
|
|
|
|
|
|
// state management
|
2024-02-16 09:32:52 +00:00
|
|
|
Schema collections.Schema
|
|
|
|
Params collections.Item[registrytypes.Params]
|
|
|
|
Records *collections.IndexedMap[string, registrytypes.Record, RecordsIndexes]
|
|
|
|
Authorities *collections.IndexedMap[string, registrytypes.NameAuthority, AuthoritiesIndexes]
|
2024-02-15 07:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewKeeper creates a new Keeper instance
|
|
|
|
func NewKeeper(
|
|
|
|
cdc codec.BinaryCodec,
|
|
|
|
storeService storetypes.KVStoreService,
|
|
|
|
accountKeeper auth.AccountKeeper,
|
|
|
|
bankKeeper bank.Keeper,
|
|
|
|
recordKeeper RecordKeeper,
|
|
|
|
bondKeeper bondkeeper.Keeper,
|
|
|
|
auctionKeeper auctionkeeper.Keeper,
|
|
|
|
) Keeper {
|
|
|
|
sb := collections.NewSchemaBuilder(storeService)
|
|
|
|
k := Keeper{
|
2024-02-16 06:40:42 +00:00
|
|
|
cdc: cdc,
|
|
|
|
accountKeeper: accountKeeper,
|
|
|
|
bankKeeper: bankKeeper,
|
|
|
|
recordKeeper: recordKeeper,
|
|
|
|
bondKeeper: bondKeeper,
|
|
|
|
auctionKeeper: auctionKeeper,
|
|
|
|
Params: collections.NewItem(sb, registrytypes.ParamsPrefix, "params", codec.CollValue[registrytypes.Params](cdc)),
|
2024-02-16 09:32:52 +00:00
|
|
|
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),
|
|
|
|
),
|
2024-02-15 07:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
schema, err := sb.Build()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
k.Schema = schema
|
|
|
|
|
|
|
|
return k
|
|
|
|
}
|
2024-02-16 06:40:42 +00:00
|
|
|
|
|
|
|
// HasRecord - checks if a record by the given id exists.
|
|
|
|
func (k Keeper) HasRecord(ctx sdk.Context, id string) (bool, error) {
|
|
|
|
has, err := k.Records.Has(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return has, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListRecords - get all records.
|
|
|
|
func (k Keeper) ListRecords(ctx sdk.Context) ([]registrytypes.Record, error) {
|
|
|
|
iter, err := k.Records.Iterate(ctx, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Check if required
|
|
|
|
// decodeRecordNames(store, &record)
|
|
|
|
|
|
|
|
return iter.Values()
|
|
|
|
}
|
|
|
|
|
2024-02-16 06:48:43 +00:00
|
|
|
// 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)
|
|
|
|
if err != nil {
|
|
|
|
return registrytypes.Record{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return record, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetRecordsByBondId - gets a record from the store.
|
|
|
|
func (k Keeper) GetRecordsByBondId(ctx sdk.Context, bondId string) ([]registrytypes.Record, error) {
|
|
|
|
iter, err := k.Records.Indexes.BondId.MatchExact(ctx, bondId)
|
|
|
|
if err != nil {
|
|
|
|
return []registrytypes.Record{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return indexes.CollectValues(ctx, k.Records, iter)
|
|
|
|
}
|
|
|
|
|
2024-02-16 06:40:42 +00:00
|
|
|
// RecordsFromAttributes gets a list of records whose attributes match all provided values
|
|
|
|
func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*registrytypes.QueryRecordsRequest_KeyValueInput, all bool) ([]registrytypes.Record, error) {
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k Keeper) GetRecordExpiryQueue(ctx sdk.Context) []*registrytypes.ExpiryQueueRecord {
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutRecord - saves a record to the store.
|
|
|
|
func (k Keeper) SaveRecord(ctx sdk.Context, record registrytypes.Record) error {
|
|
|
|
return k.Records.Set(ctx, record.Id, record)
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
// k.updateBlockChangeSetForRecord(ctx, record.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessSetRecord creates a record.
|
|
|
|
func (k Keeper) SetRecord(ctx sdk.Context, msg registrytypes.MsgSetRecord) (*registrytypes.ReadableRecord, error) {
|
|
|
|
payload := msg.Payload.ToReadablePayload()
|
|
|
|
record := registrytypes.ReadableRecord{Attributes: payload.RecordAttributes, BondId: msg.BondId}
|
|
|
|
|
|
|
|
// Check signatures.
|
|
|
|
resourceSignBytes, _ := record.GetSignBytes()
|
|
|
|
cid, err := record.GetCid()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Invalid record JSON")
|
|
|
|
}
|
|
|
|
|
|
|
|
record.Id = cid
|
|
|
|
|
|
|
|
has, err := k.HasRecord(ctx, record.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if has {
|
|
|
|
// Immutable record already exists. No-op.
|
|
|
|
return &record, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
record.Owners = []string{}
|
|
|
|
for _, sig := range payload.Signatures {
|
|
|
|
pubKey, err := legacy.PubKeyFromBytes(helpers.BytesFromBase64(sig.PubKey))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprint("Error decoding pubKey from bytes: ", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
sigOK := pubKey.VerifySignature(resourceSignBytes, helpers.BytesFromBase64(sig.Sig))
|
|
|
|
if !sigOK {
|
|
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprint("Signature mismatch: ", sig.PubKey))
|
|
|
|
}
|
|
|
|
record.Owners = append(record.Owners, pubKey.Address().String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort owners list.
|
|
|
|
sort.Strings(record.Owners)
|
|
|
|
sdkErr := k.processRecord(ctx, &record, false)
|
|
|
|
if sdkErr != nil {
|
|
|
|
return nil, sdkErr
|
|
|
|
}
|
|
|
|
|
|
|
|
return &record, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k Keeper) processRecord(ctx sdk.Context, record *registrytypes.ReadableRecord, isRenewal bool) error {
|
|
|
|
params, err := k.GetParams(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
rent := params.RecordRent
|
|
|
|
if err = k.bondKeeper.TransferCoinsToModuleAccount(
|
|
|
|
ctx, record.BondId, registrytypes.RecordRentModuleAccountName, sdk.NewCoins(rent),
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
record.CreateTime = ctx.BlockHeader().Time.Format(time.RFC3339)
|
|
|
|
record.ExpiryTime = ctx.BlockHeader().Time.Add(params.RecordRentDuration).Format(time.RFC3339)
|
|
|
|
record.Deleted = false
|
|
|
|
|
|
|
|
recordObj, err := record.ToRecordObj()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save record in store.
|
|
|
|
if err = k.SaveRecord(ctx, recordObj); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO look up/validate record type here
|
|
|
|
|
|
|
|
if err := k.processAttributes(ctx, record.Attributes, record.Id, ""); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
// expiryTimeKey := GetAttributesIndexKey(ExpiryTimeAttributeName, []byte(record.ExpiryTime))
|
|
|
|
// if err := k.SetAttributeMapping(ctx, expiryTimeKey, record.ID); err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
|
|
|
|
// k.InsertRecordExpiryQueue(ctx, recordObj)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k Keeper) processAttributes(ctx sdk.Context, attrs registrytypes.AttributeMap, id string, prefix string) error {
|
|
|
|
np := basicnode.Prototype.Map
|
|
|
|
nb := np.NewBuilder()
|
|
|
|
encAttrs, err := canonicaljson.Marshal(attrs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(attrs) == 0 {
|
|
|
|
encAttrs = []byte("{}")
|
|
|
|
}
|
|
|
|
err = dagjson.Decode(nb, bytes.NewReader(encAttrs))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to decode attributes: %w", err)
|
|
|
|
}
|
|
|
|
n := nb.Build()
|
|
|
|
if n.Kind() != ipld.Kind_Map {
|
|
|
|
return fmt.Errorf("record attributes must be a map, not %T", n.Kind())
|
|
|
|
}
|
|
|
|
|
|
|
|
return k.processAttributeMap(ctx, n, id, prefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k Keeper) processAttributeMap(ctx sdk.Context, n ipld.Node, id string, prefix string) error {
|
|
|
|
for it := n.MapIterator(); !it.Done(); {
|
|
|
|
//nolint:misspell
|
|
|
|
keynode, valuenode, err := it.Next()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
key, err := keynode.AsString()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if valuenode.Kind() == ipld.Kind_Map {
|
|
|
|
err := k.processAttributeMap(ctx, valuenode, id, key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := dagjson.Encode(valuenode, &buf); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO
|
|
|
|
// value := buf.Bytes()
|
|
|
|
// indexKey := GetAttributesIndexKey(prefix+key, value)
|
|
|
|
// if err := k.SetAttributeMapping(ctx, indexKey, id); err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetModuleBalances gets the registry module account(s) balances.
|
|
|
|
func (k Keeper) GetModuleBalances(ctx sdk.Context) []*registrytypes.AccountBalance {
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|