encoding of query attrs

This commit is contained in:
Roy Crihfield 2023-10-19 08:47:21 -05:00
parent 16d9291bc2
commit 7d43cd3ee4
3 changed files with 91 additions and 42 deletions

View File

@ -129,9 +129,9 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() {
recAttr := helpers.UnMarshalMapFromJSONBytes(record.Attributes) recAttr := helpers.UnMarshalMapFromJSONBytes(record.Attributes)
for _, attr := range test.req.GetAttributes() { for _, attr := range test.req.GetAttributes() {
if attr.Key[:4] == "x500" { 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 { } else {
sr.Equal(keeper.GetAttributeValue(attr.Value), recAttr[attr.Key]) sr.Equal(keeper.EncodeAttributeValue(attr.Value), recAttr[attr.Key])
} }
} }
} }

View File

@ -22,7 +22,10 @@ import (
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/tendermint/tendermint/libs/log" "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" "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" 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 // 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) { func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*types.QueryListRecordsRequest_KeyValueInput, all bool) ([]types.Record, error) {
log := logger(ctx).With("function", "RecordsFromAttributes")
resultRecordIds := []string{} resultRecordIds := []string{}
for i, attr := range attributes { 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) 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) recordIds, err := k.GetAttributeMapping(ctx, attributeIndex)
if err != nil { if err != nil {
return nil, err return nil, err
@ -178,27 +191,38 @@ func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*types.Query
return records, nil 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) { switch value := input.GetValue().(type) {
case *types.QueryListRecordsRequest_ValueInput_String_: case *types.QueryListRecordsRequest_ValueInput_String_:
return value.String_ nb.AssignString(value.String_)
case *types.QueryListRecordsRequest_ValueInput_Int: case *types.QueryListRecordsRequest_ValueInput_Int:
return value.Int nb.AssignInt(value.Int)
case *types.QueryListRecordsRequest_ValueInput_Float: case *types.QueryListRecordsRequest_ValueInput_Float:
return value.Float nb.AssignFloat(value.Float)
case *types.QueryListRecordsRequest_ValueInput_Boolean: case *types.QueryListRecordsRequest_ValueInput_Boolean:
return value.Boolean nb.AssignBool(value.Boolean)
case *types.QueryListRecordsRequest_ValueInput_Link: case *types.QueryListRecordsRequest_ValueInput_Link:
return value.Link link := cidlink.Link{Cid: cid.MustParse(value.Link)}
nb.AssignLink(link)
case *types.QueryListRecordsRequest_ValueInput_Array: case *types.QueryListRecordsRequest_ValueInput_Array:
return value.Array // TODO
case *types.QueryListRecordsRequest_ValueInput_Map: case *types.QueryListRecordsRequest_ValueInput_Map:
return value.Map // TODO
case nil:
return nil
default: 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 { func getIntersection(a []string, b []string) []string {
@ -251,7 +275,7 @@ func (k Keeper) GetRecordExpiryQueue(ctx sdk.Context) []*types.ExpiryQueueRecord
// ProcessSetRecord creates a record. // ProcessSetRecord creates a record.
func (k Keeper) ProcessSetRecord(ctx sdk.Context, msg types.MsgSetRecord) (*types.RecordEncodable, error) { func (k Keeper) ProcessSetRecord(ctx sdk.Context, msg types.MsgSetRecord) (*types.RecordEncodable, error) {
payload := msg.Payload.ToReadablePayload() payload := msg.Payload.ToReadablePayload()
record := types.RecordEncodable{Attributes: payload.Record, BondID: msg.BondId} record := types.RecordEncodable{Attributes: payload.RecordAttributes, BondID: msg.BondId}
// Check signatures. // Check signatures.
resourceSignBytes, _ := record.GetSignBytes() resourceSignBytes, _ := record.GetSignBytes()
@ -342,19 +366,40 @@ func (k Keeper) PutRecord(ctx sdk.Context, record types.Record) {
k.updateBlockChangeSetForRecord(ctx, record.Id) k.updateBlockChangeSetForRecord(ctx, record.Id)
} }
func (k Keeper) processAttributes(ctx sdk.Context, attrs map[string]any, id string, prefix string) error { func (k Keeper) processAttributes(ctx sdk.Context, attrs []byte, id string, prefix string) error {
np := basicnode.Prototype.Any // Pick a stle for the in-memory data. np := basicnode.Prototype.Map
nb := np.NewBuilder() // Create a builder. nb := np.NewBuilder()
err := dagjson.Decode(nb, bytes.NewReader(content)) // Hand the builder to decoding -- decoding will fill it in! err := dagjson.Decode(nb, bytes.NewReader(attrs))
if err != nil { if err != nil {
return "", err return 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)
} }
n := nb.Build() // Call 'Build' to get the resulting Node. (It's immutable!)
for key, value := range attrs { func (k Keeper) processAttributeMap(ctx sdk.Context, n ipld.Node, id string, prefix string) error {
if subRecord, ok := value.(map[string]any); ok { for it := n.MapIterator(); !it.Done(); {
k.processAttributes(ctx, subRecord, id, key) 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 { } else {
var buf bytes.Buffer
if err := dagjson.Encode(valuenode, &buf); err != nil {
return err
}
value := buf.Bytes()
indexKey := GetAttributesIndexKey(prefix+key, value) indexKey := GetAttributesIndexKey(prefix+key, value)
if err := k.SetAttributeMapping(ctx, indexKey, id); err != nil { if err := k.SetAttributeMapping(ctx, indexKey, id); err != nil {
return err return err

View File

@ -17,9 +17,15 @@ const (
// become specific to content records. schema records will either occupy a new message or have new // become specific to content records. schema records will either occupy a new message or have new
// more general purpose helper types. // 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. // PayloadEncodable represents a signed record payload that can be serialized from/to YAML.
type PayloadEncodable struct { type PayloadEncodable struct {
Record map[string]interface{} `json:"record"` RecordAttributes DagJsonBlob `json:"record"`
Signatures []Signature `json:"signatures"` Signatures []Signature `json:"signatures"`
} }
@ -27,7 +33,7 @@ type PayloadEncodable struct {
// Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4. // Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4.
func (payloadObj *PayloadEncodable) ToPayload() Payload { func (payloadObj *PayloadEncodable) ToPayload() Payload {
// Note: record directly contains the attributes here // Note: record directly contains the attributes here
attributes := helpers.MarshalMapToJSONBytes(payloadObj.Record) attributes := payloadObj.RecordAttributes
payload := Payload{ payload := Payload{
Record: &Record{ Record: &Record{
Deleted: false, Deleted: false,
@ -36,7 +42,6 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload {
}, },
Signatures: payloadObj.Signatures, Signatures: payloadObj.Signatures,
} }
// TODO rm error
return payload return payload
} }
@ -44,7 +49,7 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload {
func (payload Payload) ToReadablePayload() PayloadEncodable { func (payload Payload) ToReadablePayload() PayloadEncodable {
var encodable PayloadEncodable var encodable PayloadEncodable
encodable.Record = helpers.UnMarshalMapFromJSONBytes(payload.Record.Attributes) encodable.RecordAttributes = payload.Record.Attributes
encodable.Signatures = payload.Signatures encodable.Signatures = payload.Signatures
return encodable return encodable
@ -61,7 +66,7 @@ func (r *Record) ToReadableRecord() RecordEncodable {
resourceObj.Deleted = r.Deleted resourceObj.Deleted = r.Deleted
resourceObj.Owners = r.Owners resourceObj.Owners = r.Owners
resourceObj.Names = r.Names resourceObj.Names = r.Names
resourceObj.Attributes = helpers.UnMarshalMapFromJSONBytes(r.Attributes) resourceObj.Attributes = r.Attributes
return resourceObj return resourceObj
} }
@ -75,14 +80,13 @@ type RecordEncodable struct {
ExpiryTime string `json:"expiryTime,omitempty"` ExpiryTime string `json:"expiryTime,omitempty"`
Deleted bool `json:"deleted,omitempty"` Deleted bool `json:"deleted,omitempty"`
Owners []string `json:"owners,omitempty"` Owners []string `json:"owners,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"` // Attributes map[string]interface{} `json:"attributes,omitempty"`
Attributes DagJsonBlob `json:"attributes,omitempty"`
} }
// ToRecordObj converts Record to RecordObj. // ToRecordObj converts Record to RecordObj.
// Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4. // Why? Because go-amino can't handle maps: https://github.com/tendermint/go-amino/issues/4.
func (r *RecordEncodable) ToRecordObj() (Record, error) { func (r *RecordEncodable) ToRecordObj() (Record, error) {
attributes := helpers.MarshalMapToJSONBytes(r.Attributes)
var resourceObj Record var resourceObj Record
resourceObj.Id = r.ID resourceObj.Id = r.ID
@ -91,7 +95,7 @@ func (r *RecordEncodable) ToRecordObj() (Record, error) {
resourceObj.ExpiryTime = r.ExpiryTime resourceObj.ExpiryTime = r.ExpiryTime
resourceObj.Deleted = r.Deleted resourceObj.Deleted = r.Deleted
resourceObj.Owners = r.Owners resourceObj.Owners = r.Owners
resourceObj.Attributes = attributes resourceObj.Attributes = r.Attributes
return resourceObj, nil return resourceObj, nil
} }
@ -100,7 +104,7 @@ func (r *RecordEncodable) ToRecordObj() (Record, error) {
func (r *RecordEncodable) CanonicalJSON() []byte { func (r *RecordEncodable) CanonicalJSON() []byte {
bytes, err := canonicalJson.Marshal(r.Attributes) bytes, err := canonicalJson.Marshal(r.Attributes)
if err != nil { if err != nil {
panic("Record marshal error.") panic("Record marshal error: " + err.Error())
} }
return bytes return bytes