Graphql schema for DAG-JSON objects #112
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user