From 7d43cd3ee4312c369ef0f0a26d7036b97947f838 Mon Sep 17 00:00:00 2001 From: Roy Crihfield Date: Thu, 19 Oct 2023 08:47:21 -0500 Subject: [PATCH] encoding of query attrs --- x/registry/keeper/grpc_query_test.go | 4 +- x/registry/keeper/keeper.go | 89 +++++++++++++++++++++------- x/registry/types/types.go | 40 +++++++------ 3 files changed, 91 insertions(+), 42 deletions(-) diff --git a/x/registry/keeper/grpc_query_test.go b/x/registry/keeper/grpc_query_test.go index 5edaf647..e51a9326 100644 --- a/x/registry/keeper/grpc_query_test.go +++ b/x/registry/keeper/grpc_query_test.go @@ -129,9 +129,9 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() { recAttr := helpers.UnMarshalMapFromJSONBytes(record.Attributes) for _, attr := range test.req.GetAttributes() { if attr.Key[:4] == "x500" { - sr.Equal(keeper.GetAttributeValue(attr.Value), recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) + sr.Equal(keeper.EncodeAttributeValue(attr.Value), recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) } else { - sr.Equal(keeper.GetAttributeValue(attr.Value), recAttr[attr.Key]) + sr.Equal(keeper.EncodeAttributeValue(attr.Value), recAttr[attr.Key]) } } } diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index a96f31a8..da0cca85 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -22,7 +22,10 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/tendermint/tendermint/libs/log" + cid "github.com/ipfs/go-cid" + "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/codec/dagjson" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" basicnode "github.com/ipld/go-ipld-prime/node/basic" ) @@ -147,10 +150,20 @@ func (k Keeper) ListRecords(ctx sdk.Context) []types.Record { // RecordsFromAttributes gets a list of records whose attributes match all provided values func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*types.QueryListRecordsRequest_KeyValueInput, all bool) ([]types.Record, error) { + log := logger(ctx).With("function", "RecordsFromAttributes") + resultRecordIds := []string{} for i, attr := range attributes { - val := GetAttributeValue(attr.Value) + val, err := EncodeAttributeValue2(attr.Value) + if err != nil { + return nil, err + } attributeIndex := GetAttributesIndexKey(attr.Key, val) + log.Debug("attribute index", + "key", attr.Key, + "value", val, + "value_type", fmt.Sprintf("%T", val), + "index", attributeIndex) recordIds, err := k.GetAttributeMapping(ctx, attributeIndex) if err != nil { return nil, err @@ -178,27 +191,38 @@ func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*types.Query return records, nil } -func GetAttributeValue(input *types.QueryListRecordsRequest_ValueInput) interface{} { +// TODO non recursive +func EncodeAttributeValue2(input *types.QueryListRecordsRequest_ValueInput) ([]byte, error) { + np := basicnode.Prototype.Any + nb := np.NewBuilder() + switch value := input.GetValue().(type) { case *types.QueryListRecordsRequest_ValueInput_String_: - return value.String_ + nb.AssignString(value.String_) case *types.QueryListRecordsRequest_ValueInput_Int: - return value.Int + nb.AssignInt(value.Int) case *types.QueryListRecordsRequest_ValueInput_Float: - return value.Float + nb.AssignFloat(value.Float) case *types.QueryListRecordsRequest_ValueInput_Boolean: - return value.Boolean + nb.AssignBool(value.Boolean) case *types.QueryListRecordsRequest_ValueInput_Link: - return value.Link + link := cidlink.Link{Cid: cid.MustParse(value.Link)} + nb.AssignLink(link) case *types.QueryListRecordsRequest_ValueInput_Array: - return value.Array + // TODO case *types.QueryListRecordsRequest_ValueInput_Map: - return value.Map - case nil: - return nil + // TODO default: - return fmt.Errorf("Value has unepxpected type %T", value) + return nil, fmt.Errorf("Value has unepxpected type %T", value) } + + n := nb.Build() + var buf bytes.Buffer + if err := dagjson.Encode(n, &buf); err != nil { + return nil, err + } + value := buf.Bytes() + return value, nil } func getIntersection(a []string, b []string) []string { @@ -251,7 +275,7 @@ func (k Keeper) GetRecordExpiryQueue(ctx sdk.Context) []*types.ExpiryQueueRecord // ProcessSetRecord creates a record. func (k Keeper) ProcessSetRecord(ctx sdk.Context, msg types.MsgSetRecord) (*types.RecordEncodable, error) { payload := msg.Payload.ToReadablePayload() - record := types.RecordEncodable{Attributes: payload.Record, BondID: msg.BondId} + record := types.RecordEncodable{Attributes: payload.RecordAttributes, BondID: msg.BondId} // Check signatures. resourceSignBytes, _ := record.GetSignBytes() @@ -342,19 +366,40 @@ func (k Keeper) PutRecord(ctx sdk.Context, record types.Record) { k.updateBlockChangeSetForRecord(ctx, record.Id) } -func (k Keeper) processAttributes(ctx sdk.Context, attrs map[string]any, id string, prefix string) error { - np := basicnode.Prototype.Any // Pick a stle for the in-memory data. - nb := np.NewBuilder() // Create a builder. - err := dagjson.Decode(nb, bytes.NewReader(content)) // Hand the builder to decoding -- decoding will fill it in! +func (k Keeper) processAttributes(ctx sdk.Context, attrs []byte, id string, prefix string) error { + np := basicnode.Prototype.Map + nb := np.NewBuilder() + err := dagjson.Decode(nb, bytes.NewReader(attrs)) if err != nil { - return "", err + return err } - n := nb.Build() // Call 'Build' to get the resulting Node. (It's immutable!) + 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) +} - for key, value := range attrs { - if subRecord, ok := value.(map[string]any); ok { - k.processAttributes(ctx, subRecord, id, key) +func (k Keeper) processAttributeMap(ctx sdk.Context, n ipld.Node, id string, prefix string) error { + for it := n.MapIterator(); !it.Done(); { + keynode, valuenode, err := it.Next() + if err != nil { + return err + } + key, err := keynode.AsString() + if err != nil { + return err + } + + // for key, value := range attrs { + if valuenode.Kind() == ipld.Kind_Map { + k.processAttributeMap(ctx, valuenode, id, key) } else { + var buf bytes.Buffer + if err := dagjson.Encode(valuenode, &buf); err != nil { + return err + } + value := buf.Bytes() indexKey := GetAttributesIndexKey(prefix+key, value) if err := k.SetAttributeMapping(ctx, indexKey, id); err != nil { return err diff --git a/x/registry/types/types.go b/x/registry/types/types.go index 6bd466b5..2c7d5bb4 100644 --- a/x/registry/types/types.go +++ b/x/registry/types/types.go @@ -17,17 +17,23 @@ const ( // become specific to content records. schema records will either occupy a new message or have new // more general purpose helper types. +type DagJsonBlob []byte + +func (b DagJsonBlob) MarshalJSON() ([]byte, error) { + return b, nil +} + // PayloadEncodable represents a signed record payload that can be serialized from/to YAML. type PayloadEncodable struct { - Record map[string]interface{} `json:"record"` - Signatures []Signature `json:"signatures"` + RecordAttributes DagJsonBlob `json:"record"` + Signatures []Signature `json:"signatures"` } // ToPayload converts PayloadEncodable to Payload object. // Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4. func (payloadObj *PayloadEncodable) ToPayload() Payload { // Note: record directly contains the attributes here - attributes := helpers.MarshalMapToJSONBytes(payloadObj.Record) + attributes := payloadObj.RecordAttributes payload := Payload{ Record: &Record{ Deleted: false, @@ -36,7 +42,6 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload { }, Signatures: payloadObj.Signatures, } - // TODO rm error return payload } @@ -44,7 +49,7 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload { func (payload Payload) ToReadablePayload() PayloadEncodable { var encodable PayloadEncodable - encodable.Record = helpers.UnMarshalMapFromJSONBytes(payload.Record.Attributes) + encodable.RecordAttributes = payload.Record.Attributes encodable.Signatures = payload.Signatures return encodable @@ -61,28 +66,27 @@ func (r *Record) ToReadableRecord() RecordEncodable { resourceObj.Deleted = r.Deleted resourceObj.Owners = r.Owners resourceObj.Names = r.Names - resourceObj.Attributes = helpers.UnMarshalMapFromJSONBytes(r.Attributes) + resourceObj.Attributes = r.Attributes return resourceObj } // RecordEncodable represents a WNS record. type RecordEncodable struct { - ID string `json:"id,omitempty"` - Names []string `json:"names,omitempty"` - BondID string `json:"bondId,omitempty"` - CreateTime string `json:"createTime,omitempty"` - ExpiryTime string `json:"expiryTime,omitempty"` - Deleted bool `json:"deleted,omitempty"` - Owners []string `json:"owners,omitempty"` - Attributes map[string]interface{} `json:"attributes,omitempty"` + ID string `json:"id,omitempty"` + Names []string `json:"names,omitempty"` + BondID string `json:"bondId,omitempty"` + CreateTime string `json:"createTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` + Deleted bool `json:"deleted,omitempty"` + Owners []string `json:"owners,omitempty"` + // Attributes map[string]interface{} `json:"attributes,omitempty"` + Attributes DagJsonBlob `json:"attributes,omitempty"` } // ToRecordObj converts Record to RecordObj. // Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4. func (r *RecordEncodable) ToRecordObj() (Record, error) { - attributes := helpers.MarshalMapToJSONBytes(r.Attributes) - var resourceObj Record resourceObj.Id = r.ID @@ -91,7 +95,7 @@ func (r *RecordEncodable) ToRecordObj() (Record, error) { resourceObj.ExpiryTime = r.ExpiryTime resourceObj.Deleted = r.Deleted resourceObj.Owners = r.Owners - resourceObj.Attributes = attributes + resourceObj.Attributes = r.Attributes return resourceObj, nil } @@ -100,7 +104,7 @@ func (r *RecordEncodable) ToRecordObj() (Record, error) { func (r *RecordEncodable) CanonicalJSON() []byte { bytes, err := canonicalJson.Marshal(r.Attributes) if err != nil { - panic("Record marshal error.") + panic("Record marshal error: " + err.Error()) } return bytes