Graphql schema for DAG-JSON objects #112

Closed
roysc wants to merge 10 commits from roy/graphql-to-ipld into main
4 changed files with 77 additions and 76 deletions
Showing only changes of commit d64e80f963 - Show all commits

View File

@ -47,24 +47,20 @@ func Int64ToBytes(num int64) []byte {
return buf.Bytes() return buf.Bytes()
} }
// MarshalMapToJSONBytes converts map[string]interface{} to bytes. func MustMarshalJSON[T any](val T) (bytes []byte) {
func MarshalMapToJSONBytes(val map[string]interface{}) (bytes []byte) {
bytes, err := json.Marshal(val) bytes, err := json.Marshal(val)
if err != nil { if err != nil {
panic("Marshal error.") panic("JSON marshal error:" + err.Error())
} }
return return
} }
// UnMarshalMapFromJSONBytes converts bytes to map[string]interface{}. func MustUnmarshalJSON[T any](bytes []byte) T {
func UnMarshalMapFromJSONBytes(bytes []byte) map[string]interface{} { var val T
var val map[string]interface{}
err := json.Unmarshal(bytes, &val) err := json.Unmarshal(bytes, &val)
if err != nil { if err != nil {
panic("Unmarshal error.") panic("JSON unmarshal error:" + err.Error())
} }
return val return val
} }

View File

@ -126,12 +126,16 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() {
sr.Equal(resp.GetRecords()[0].GetBondId(), suite.bond.GetId()) sr.Equal(resp.GetRecords()[0].GetBondId(), suite.bond.GetId())
for _, record := range resp.GetRecords() { for _, record := range resp.GetRecords() {
recAttr := helpers.UnMarshalMapFromJSONBytes(record.Attributes) recAttr := helpers.MustUnmarshalJSON[registrytypes.AttributeMap](record.Attributes)
for _, attr := range test.req.GetAttributes() { for _, attr := range test.req.GetAttributes() {
enc, err := keeper.QueryValueToJSON(attr.Value)
sr.NoError(err)
expected := helpers.MustUnmarshalJSON[any](enc)
if attr.Key[:4] == "x500" { if attr.Key[:4] == "x500" {
sr.Equal(keeper.EncodeAttributeValue(attr.Value), recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) sr.Equal(expected, recAttr["x500"].(map[string]interface{})[attr.Key[4:]])
} else { } else {
sr.Equal(keeper.EncodeAttributeValue(attr.Value), recAttr[attr.Key]) sr.Equal(expected, recAttr[attr.Key])
} }
} }
} }

View File

@ -8,10 +8,6 @@ import (
"time" "time"
errorsmod "cosmossdk.io/errors" errorsmod "cosmossdk.io/errors"
auctionkeeper "github.com/cerc-io/laconicd/x/auction/keeper"
bondkeeper "github.com/cerc-io/laconicd/x/bond/keeper"
"github.com/cerc-io/laconicd/x/registry/helpers"
"github.com/cerc-io/laconicd/x/registry/types"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/codec/legacy"
storetypes "github.com/cosmos/cosmos-sdk/store/types" storetypes "github.com/cosmos/cosmos-sdk/store/types"
@ -20,13 +16,18 @@ import (
auth "github.com/cosmos/cosmos-sdk/x/auth/keeper" auth "github.com/cosmos/cosmos-sdk/x/auth/keeper"
bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" bank "github.com/cosmos/cosmos-sdk/x/bank/keeper"
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/gibson042/canonicaljson-go"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime" "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" 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"
"github.com/tendermint/tendermint/libs/log"
auctionkeeper "github.com/cerc-io/laconicd/x/auction/keeper"
bondkeeper "github.com/cerc-io/laconicd/x/bond/keeper"
"github.com/cerc-io/laconicd/x/registry/helpers"
"github.com/cerc-io/laconicd/x/registry/types"
) )
var ( var (
@ -152,7 +153,7 @@ func (k Keeper) ListRecords(ctx sdk.Context) []types.Record {
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) {
resultRecordIds := []string{} resultRecordIds := []string{}
for i, attr := range attributes { for i, attr := range attributes {
suffix, err := EncodeQueryValue(attr.Value) suffix, err := QueryValueToJSON(attr.Value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -184,8 +185,9 @@ func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*types.Query
return records, nil return records, nil
} }
// TODO not recursive, and only should be if we want to support querying with whole sub-objects // TODO not recursive, and only should be if we want to support querying with whole sub-objects,
func EncodeQueryValue(input *types.QueryListRecordsRequest_ValueInput) ([]byte, error) { // which seems unnecessary.
func QueryValueToJSON(input *types.QueryListRecordsRequest_ValueInput) ([]byte, error) {
np := basicnode.Prototype.Any np := basicnode.Prototype.Any
nb := np.NewBuilder() nb := np.NewBuilder()
@ -202,20 +204,19 @@ func EncodeQueryValue(input *types.QueryListRecordsRequest_ValueInput) ([]byte,
link := cidlink.Link{Cid: cid.MustParse(value.Link)} link := cidlink.Link{Cid: cid.MustParse(value.Link)}
nb.AssignLink(link) nb.AssignLink(link)
case *types.QueryListRecordsRequest_ValueInput_Array: case *types.QueryListRecordsRequest_ValueInput_Array:
// TODO return nil, fmt.Errorf("Recursive query values are not supported")
case *types.QueryListRecordsRequest_ValueInput_Map: case *types.QueryListRecordsRequest_ValueInput_Map:
// TODO return nil, fmt.Errorf("Recursive query values are not supported")
default: default:
return nil, fmt.Errorf("Value has unepxpected type %T", value) return nil, fmt.Errorf("Value has unexpected type %T", value)
} }
n := nb.Build() n := nb.Build()
var buf bytes.Buffer var buf bytes.Buffer
if err := dagjson.Encode(n, &buf); err != nil { if err := dagjson.Encode(n, &buf); err != nil {
return nil, err return nil, fmt.Errorf("encoding value to JSON failed: %w", err)
} }
value := buf.Bytes() return buf.Bytes(), nil
return value, nil
} }
func getIntersection(a []string, b []string) []string { func getIntersection(a []string, b []string) []string {
@ -288,14 +289,12 @@ func (k Keeper) ProcessSetRecord(ctx sdk.Context, msg types.MsgSetRecord) (*type
for _, sig := range payload.Signatures { for _, sig := range payload.Signatures {
pubKey, err := legacy.PubKeyFromBytes(helpers.BytesFromBase64(sig.PubKey)) pubKey, err := legacy.PubKeyFromBytes(helpers.BytesFromBase64(sig.PubKey))
if err != nil { if err != nil {
fmt.Println("Error decoding pubKey from bytes: ", err) return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprint("Error decoding pubKey from bytes: ", err))
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Invalid public key.")
} }
sigOK := pubKey.VerifySignature(resourceSignBytes, helpers.BytesFromBase64(sig.Sig)) sigOK := pubKey.VerifySignature(resourceSignBytes, helpers.BytesFromBase64(sig.Sig))
if !sigOK { if !sigOK {
fmt.Println("Signature mismatch: ", sig.PubKey) return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprint("Signature mismatch: ", sig.PubKey))
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Invalid signature.")
} }
record.Owners = append(record.Owners, pubKey.Address().String()) record.Owners = append(record.Owners, pubKey.Address().String())
} }
@ -359,17 +358,25 @@ 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 []byte, id string, prefix string) error { func (k Keeper) processAttributes(ctx sdk.Context, attrs types.AttributeMap, id string, prefix string) error {
np := basicnode.Prototype.Map np := basicnode.Prototype.Map
nb := np.NewBuilder() nb := np.NewBuilder()
err := dagjson.Decode(nb, bytes.NewReader(attrs)) encAttrs, err := canonicaljson.Marshal(attrs)
if err != nil { if err != nil {
return err 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() n := nb.Build()
if n.Kind() != ipld.Kind_Map { if n.Kind() != ipld.Kind_Map {
return fmt.Errorf("Record attributes must be a map, not %T", n.Kind()) return fmt.Errorf("Record attributes must be a map, not %T", n.Kind())
} }
return k.processAttributeMap(ctx, n, id, prefix) return k.processAttributeMap(ctx, n, id, prefix)
} }
@ -404,7 +411,6 @@ func (k Keeper) processAttributeMap(ctx sdk.Context, n ipld.Node, id string, pre
func GetAttributesIndexKey(key string, suffix []byte) []byte { func GetAttributesIndexKey(key string, suffix []byte) []byte {
keyString := fmt.Sprintf("%s%s", key, suffix) keyString := fmt.Sprintf("%s%s", key, suffix)
return append(PrefixAttributesIndex, []byte(keyString)...) return append(PrefixAttributesIndex, []byte(keyString)...)
// return append(append(PrefixAttributesIndex, key...), suffix...)
} }
func (k Keeper) SetAttributeMapping(ctx sdk.Context, key []byte, recordID string) error { func (k Keeper) SetAttributeMapping(ctx sdk.Context, key []byte, recordID string) error {

View File

@ -4,7 +4,7 @@ import (
"crypto/sha256" "crypto/sha256"
"github.com/cerc-io/laconicd/x/registry/helpers" "github.com/cerc-io/laconicd/x/registry/helpers"
canonicalJson "github.com/gibson042/canonicaljson-go" "github.com/gibson042/canonicaljson-go"
) )
const ( const (
@ -17,16 +17,24 @@ 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 type AttributeMap map[string]interface{}
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 {
RecordAttributes DagJsonBlob `json:"record"` RecordAttributes AttributeMap `json:"record" yaml:"record"`
Signatures []Signature `json:"signatures"` Signatures []Signature `json:"signatures" yaml:"signatures"`
}
// 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 AttributeMap `json:"attributes,omitempty"`
} }
// ToPayload converts PayloadEncodable to Payload object. // ToPayload converts PayloadEncodable to Payload object.
@ -38,7 +46,7 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload {
Record: &Record{ Record: &Record{
Deleted: false, Deleted: false,
Owners: nil, Owners: nil,
Attributes: attributes, Attributes: helpers.MustMarshalJSON(attributes),
}, },
Signatures: payloadObj.Signatures, Signatures: payloadObj.Signatures,
} }
@ -49,41 +57,12 @@ func (payloadObj *PayloadEncodable) ToPayload() Payload {
func (payload Payload) ToReadablePayload() PayloadEncodable { func (payload Payload) ToReadablePayload() PayloadEncodable {
var encodable PayloadEncodable var encodable PayloadEncodable
encodable.RecordAttributes = payload.Record.Attributes encodable.RecordAttributes = helpers.MustUnmarshalJSON[AttributeMap](payload.Record.Attributes)
encodable.Signatures = payload.Signatures encodable.Signatures = payload.Signatures
return encodable return encodable
} }
// ToReadableRecord converts Record to a serializable object
func (r *Record) ToReadableRecord() RecordEncodable {
var resourceObj RecordEncodable
resourceObj.ID = r.Id
resourceObj.BondID = r.BondId
resourceObj.CreateTime = r.CreateTime
resourceObj.ExpiryTime = r.ExpiryTime
resourceObj.Deleted = r.Deleted
resourceObj.Owners = r.Owners
resourceObj.Names = r.Names
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"`
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) {
@ -95,16 +74,32 @@ 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 = r.Attributes resourceObj.Attributes = helpers.MustMarshalJSON(r.Attributes)
return resourceObj, nil return resourceObj, nil
} }
// ToReadableRecord converts Record to a serializable object
func (r *Record) ToReadableRecord() RecordEncodable {
var resourceObj RecordEncodable
resourceObj.ID = r.Id
resourceObj.BondID = r.BondId
resourceObj.CreateTime = r.CreateTime
resourceObj.ExpiryTime = r.ExpiryTime
resourceObj.Deleted = r.Deleted
resourceObj.Owners = r.Owners
resourceObj.Names = r.Names
resourceObj.Attributes = helpers.MustUnmarshalJSON[AttributeMap](r.Attributes)
return resourceObj
}
// CanonicalJSON returns the canonical JSON representation of the record. // CanonicalJSON returns the canonical JSON representation of the record.
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: " + err.Error()) panic("error marshalling record: " + err.Error())
} }
return bytes return bytes