2024-11-26 22:05:50 -05:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-10-03 14:45:52 -04:00
|
|
|
"fmt"
|
2024-11-26 22:05:50 -05:00
|
|
|
|
2025-10-03 14:45:52 -04:00
|
|
|
"cosmossdk.io/errors"
|
|
|
|
|
"cosmossdk.io/orm/types/ormerrors"
|
2024-11-26 22:05:50 -05:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2025-10-03 14:45:52 -04:00
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/types/query"
|
|
|
|
|
"github.com/ipfs/go-cid"
|
2024-11-26 22:05:50 -05:00
|
|
|
|
2025-10-03 14:45:52 -04:00
|
|
|
apiv1 "github.com/sonr-io/sonr/api/dwn/v1"
|
|
|
|
|
"github.com/sonr-io/sonr/x/dwn/types"
|
2024-11-26 22:05:50 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ types.QueryServer = Querier{}
|
|
|
|
|
|
|
|
|
|
type Querier struct {
|
|
|
|
|
Keeper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewQuerier(keeper Keeper) Querier {
|
|
|
|
|
return Querier{Keeper: keeper}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 14:45:52 -04:00
|
|
|
func (k Querier) Params(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryParamsRequest,
|
|
|
|
|
) (*types.QueryParamsResponse, error) {
|
2024-11-26 22:05:50 -05:00
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
p, err := k.Keeper.Params.Get(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &types.QueryParamsResponse{Params: &p}, nil
|
|
|
|
|
}
|
2025-10-03 14:45:52 -04:00
|
|
|
|
|
|
|
|
// Records queries DWN records with filters
|
|
|
|
|
func (k Querier) Records(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryRecordsRequest,
|
|
|
|
|
) (*types.QueryRecordsResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Build index key based on filters
|
|
|
|
|
var indexKey apiv1.DWNRecordIndexKey
|
|
|
|
|
|
|
|
|
|
if req.Protocol != "" {
|
|
|
|
|
indexKey = apiv1.DWNRecordTargetProtocolIndexKey{}.WithTargetProtocol(
|
|
|
|
|
req.Target,
|
|
|
|
|
req.Protocol,
|
|
|
|
|
)
|
|
|
|
|
} else if req.Schema != "" {
|
|
|
|
|
indexKey = apiv1.DWNRecordTargetSchemaIndexKey{}.WithTargetSchema(req.Target, req.Schema)
|
|
|
|
|
} else if req.ParentId != "" {
|
|
|
|
|
indexKey = apiv1.DWNRecordParentIdIndexKey{}.WithParentId(req.ParentId)
|
|
|
|
|
} else {
|
|
|
|
|
indexKey = apiv1.DWNRecordTargetProtocolIndexKey{}.WithTarget(req.Target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query with pagination
|
|
|
|
|
pageReq := req.Pagination
|
|
|
|
|
if pageReq == nil {
|
|
|
|
|
pageReq = &query.PageRequest{Limit: 100}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
records := []types.DWNRecord{}
|
|
|
|
|
pageRes := &query.PageResponse{}
|
|
|
|
|
|
|
|
|
|
iter, err := k.OrmDB.DWNRecordTable().List(ctx, indexKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to list records")
|
|
|
|
|
}
|
|
|
|
|
defer iter.Close()
|
|
|
|
|
|
|
|
|
|
count := uint64(0)
|
|
|
|
|
offset := pageReq.Offset
|
|
|
|
|
limit := pageReq.Limit
|
|
|
|
|
|
|
|
|
|
for iter.Next() {
|
|
|
|
|
record, err := iter.Value()
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply published filter
|
|
|
|
|
if req.PublishedOnly && !record.Published {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
|
|
// Handle pagination
|
|
|
|
|
if count <= offset {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if uint64(len(records)) >= limit {
|
|
|
|
|
pageRes.NextKey = []byte(record.RecordId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
records = append(records, types.ConvertAPIRecordToType(record))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageRes.Total = count
|
|
|
|
|
|
|
|
|
|
return &types.QueryRecordsResponse{
|
|
|
|
|
Records: records,
|
|
|
|
|
Pagination: pageRes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record queries a specific DWN record by ID
|
|
|
|
|
func (k Querier) Record(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryRecordRequest,
|
|
|
|
|
) (*types.QueryRecordResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.RecordId == "" {
|
|
|
|
|
return nil, types.ErrRecordIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
record, err := k.OrmDB.DWNRecordTable().Get(ctx, req.RecordId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if ormerrors.IsNotFound(err) {
|
|
|
|
|
return nil, errors.Wrapf(types.ErrRecordNotFound, "record %s not found", req.RecordId)
|
|
|
|
|
}
|
|
|
|
|
return nil, errors.Wrap(err, "failed to get record")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the record belongs to the target DWN
|
|
|
|
|
if record.Target != req.Target {
|
|
|
|
|
return nil, errors.Wrap(sdkerrors.ErrUnauthorized, "record does not belong to target DWN")
|
|
|
|
|
}
|
|
|
|
|
rec := types.ConvertAPIRecordToType(record)
|
|
|
|
|
return &types.QueryRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Protocols queries DWN protocols
|
|
|
|
|
func (k Querier) Protocols(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryProtocolsRequest,
|
|
|
|
|
) (*types.QueryProtocolsResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Query with pagination
|
|
|
|
|
pageReq := req.Pagination
|
|
|
|
|
if pageReq == nil {
|
|
|
|
|
pageReq = &query.PageRequest{Limit: 100}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protocols := []types.DWNProtocol{}
|
|
|
|
|
pageRes := &query.PageResponse{}
|
|
|
|
|
|
|
|
|
|
indexKey := apiv1.DWNProtocolTargetProtocolUriIndexKey{}.WithTarget(req.Target)
|
|
|
|
|
iter, err := k.OrmDB.DWNProtocolTable().List(ctx, indexKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to list protocols")
|
|
|
|
|
}
|
|
|
|
|
defer iter.Close()
|
|
|
|
|
|
|
|
|
|
count := uint64(0)
|
|
|
|
|
offset := pageReq.Offset
|
|
|
|
|
limit := pageReq.Limit
|
|
|
|
|
|
|
|
|
|
for iter.Next() {
|
|
|
|
|
protocol, err := iter.Value()
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply published filter
|
|
|
|
|
if req.PublishedOnly && !protocol.Published {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
|
|
// Handle pagination
|
|
|
|
|
if count <= offset {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if uint64(len(protocols)) >= limit {
|
|
|
|
|
pageRes.NextKey = []byte(protocol.ProtocolUri)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protocols = append(protocols, types.ConvertAPIProtocolToType(protocol))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageRes.Total = count
|
|
|
|
|
|
|
|
|
|
return &types.QueryProtocolsResponse{
|
|
|
|
|
Protocols: protocols,
|
|
|
|
|
Pagination: pageRes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Protocol queries a specific DWN protocol
|
|
|
|
|
func (k Querier) Protocol(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryProtocolRequest,
|
|
|
|
|
) (*types.QueryProtocolResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.ProtocolUri == "" {
|
|
|
|
|
return nil, types.ErrProtocolURIEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
protocol, err := k.OrmDB.DWNProtocolTable().Get(ctx, req.Target, req.ProtocolUri)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if ormerrors.IsNotFound(err) {
|
|
|
|
|
return nil, errors.Wrapf(
|
|
|
|
|
types.ErrProtocolNotFound,
|
|
|
|
|
"protocol %s not found",
|
|
|
|
|
req.ProtocolUri,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return nil, errors.Wrap(err, "failed to get protocol")
|
|
|
|
|
}
|
|
|
|
|
prot := types.ConvertAPIProtocolToType(protocol)
|
|
|
|
|
return &types.QueryProtocolResponse{
|
|
|
|
|
Protocol: &prot,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Permissions queries DWN permissions
|
|
|
|
|
func (k Querier) Permissions(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryPermissionsRequest,
|
|
|
|
|
) (*types.QueryPermissionsResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Build index key based on filters
|
|
|
|
|
var indexKey apiv1.DWNPermissionIndexKey
|
|
|
|
|
|
|
|
|
|
if req.Grantor != "" && req.Grantee != "" {
|
|
|
|
|
indexKey = apiv1.DWNPermissionGrantorGranteeIndexKey{}.WithGrantorGrantee(
|
|
|
|
|
req.Grantor,
|
|
|
|
|
req.Grantee,
|
|
|
|
|
)
|
|
|
|
|
} else if req.Grantor != "" {
|
|
|
|
|
indexKey = apiv1.DWNPermissionGrantorGranteeIndexKey{}.WithGrantor(req.Grantor)
|
|
|
|
|
} else if req.InterfaceName != "" && req.Method != "" {
|
|
|
|
|
indexKey = apiv1.DWNPermissionTargetInterfaceNameMethodIndexKey{}.WithTargetInterfaceNameMethod(req.Target, req.InterfaceName, req.Method)
|
|
|
|
|
} else if req.InterfaceName != "" {
|
|
|
|
|
indexKey = apiv1.DWNPermissionTargetInterfaceNameMethodIndexKey{}.WithTargetInterfaceName(req.Target, req.InterfaceName)
|
|
|
|
|
} else {
|
|
|
|
|
indexKey = apiv1.DWNPermissionTargetInterfaceNameMethodIndexKey{}.WithTarget(req.Target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query with pagination
|
|
|
|
|
pageReq := req.Pagination
|
|
|
|
|
if pageReq == nil {
|
|
|
|
|
pageReq = &query.PageRequest{Limit: 100}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
permissions := []types.DWNPermission{}
|
|
|
|
|
pageRes := &query.PageResponse{}
|
|
|
|
|
|
|
|
|
|
iter, err := k.OrmDB.DWNPermissionTable().List(ctx, indexKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to list permissions")
|
|
|
|
|
}
|
|
|
|
|
defer iter.Close()
|
|
|
|
|
|
|
|
|
|
count := uint64(0)
|
|
|
|
|
offset := pageReq.Offset
|
|
|
|
|
limit := pageReq.Limit
|
|
|
|
|
|
|
|
|
|
for iter.Next() {
|
|
|
|
|
permission, err := iter.Value()
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply filters
|
|
|
|
|
if permission.Target != req.Target {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !req.IncludeRevoked && permission.Revoked {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
|
|
// Handle pagination
|
|
|
|
|
if count <= offset {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if uint64(len(permissions)) >= limit {
|
|
|
|
|
pageRes.NextKey = []byte(permission.PermissionId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
permissions = append(permissions, types.ConvertAPIPermissionToType(permission))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageRes.Total = count
|
|
|
|
|
|
|
|
|
|
return &types.QueryPermissionsResponse{
|
|
|
|
|
Permissions: permissions,
|
|
|
|
|
Pagination: pageRes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vault queries a specific vault
|
|
|
|
|
func (k Querier) Vault(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryVaultRequest,
|
|
|
|
|
) (*types.QueryVaultResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.VaultId == "" {
|
|
|
|
|
return nil, types.ErrVaultIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
vault, err := k.OrmDB.VaultStateTable().Get(ctx, req.VaultId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if ormerrors.IsNotFound(err) {
|
|
|
|
|
return nil, errors.Wrapf(types.ErrVaultNotFound, "vault %s not found", req.VaultId)
|
|
|
|
|
}
|
|
|
|
|
return nil, errors.Wrap(err, "failed to get vault")
|
|
|
|
|
}
|
|
|
|
|
vlt := types.ConvertAPIVaultToType(vault)
|
|
|
|
|
return &types.QueryVaultResponse{
|
|
|
|
|
Vault: &vlt,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vaults queries vaults by owner
|
|
|
|
|
func (k Querier) Vaults(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryVaultsRequest,
|
|
|
|
|
) (*types.QueryVaultsResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Query with pagination
|
|
|
|
|
pageReq := req.Pagination
|
|
|
|
|
if pageReq == nil {
|
|
|
|
|
pageReq = &query.PageRequest{Limit: 100}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vaults := []types.VaultState{}
|
|
|
|
|
pageRes := &query.PageResponse{}
|
|
|
|
|
|
|
|
|
|
var iter apiv1.VaultStateIterator
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if req.Owner != "" {
|
|
|
|
|
indexKey := apiv1.VaultStateOwnerIndexKey{}.WithOwner(req.Owner)
|
|
|
|
|
iter, err = k.OrmDB.VaultStateTable().List(ctx, indexKey)
|
|
|
|
|
} else {
|
|
|
|
|
// List all vaults
|
|
|
|
|
indexKey := apiv1.VaultStatePrimaryKey{}
|
|
|
|
|
iter, err = k.OrmDB.VaultStateTable().List(ctx, indexKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to list vaults")
|
|
|
|
|
}
|
|
|
|
|
defer iter.Close()
|
|
|
|
|
|
|
|
|
|
count := uint64(0)
|
|
|
|
|
offset := pageReq.Offset
|
|
|
|
|
limit := pageReq.Limit
|
|
|
|
|
|
|
|
|
|
for iter.Next() {
|
|
|
|
|
vault, err := iter.Value()
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
|
|
// Handle pagination
|
|
|
|
|
if count <= offset {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if uint64(len(vaults)) >= limit {
|
|
|
|
|
pageRes.NextKey = []byte(vault.VaultId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vaults = append(vaults, types.ConvertAPIVaultToType(vault))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageRes.Total = count
|
|
|
|
|
|
|
|
|
|
return &types.QueryVaultsResponse{
|
|
|
|
|
Vaults: vaults,
|
|
|
|
|
Pagination: pageRes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Implement IPFS query functionality - connects to IPFS nodes and retrieves status information
|
|
|
|
|
// Should integrate with internal/ipfs package for IPFS client operations
|
|
|
|
|
// Query IPFS node health, connectivity, and peer information
|
|
|
|
|
// Return storage statistics and pinned content summary
|
|
|
|
|
// Support multiple IPFS endpoints with failover capability
|
|
|
|
|
|
|
|
|
|
// IPFS implements types.QueryServer.
|
|
|
|
|
func (k Querier) IPFS(
|
|
|
|
|
goCtx context.Context,
|
|
|
|
|
req *types.QueryIPFSRequest,
|
|
|
|
|
) (*types.QueryIPFSResponse, error) {
|
|
|
|
|
// Check if IPFS client is available
|
|
|
|
|
if k.ipfsClient == nil {
|
|
|
|
|
k.Logger().Debug("IPFS client not available")
|
|
|
|
|
return &types.QueryIPFSResponse{
|
|
|
|
|
Status: &types.IPFSStatus{
|
|
|
|
|
PeerId: "",
|
|
|
|
|
PeerName: "unavailable",
|
|
|
|
|
PeerType: "ipfs",
|
|
|
|
|
Version: "unknown",
|
|
|
|
|
},
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get node status using the new NodeStatus method
|
|
|
|
|
nodeStatus, err := k.ipfsClient.NodeStatus()
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Error("Failed to get IPFS node status", "error", err)
|
|
|
|
|
return &types.QueryIPFSResponse{
|
|
|
|
|
Status: &types.IPFSStatus{
|
|
|
|
|
PeerId: "",
|
|
|
|
|
PeerName: "connection_failed",
|
|
|
|
|
PeerType: "ipfs",
|
|
|
|
|
Version: "unknown",
|
|
|
|
|
},
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert from internal NodeStatus to types.IPFSStatus
|
|
|
|
|
status := &types.IPFSStatus{
|
|
|
|
|
PeerId: nodeStatus.PeerID,
|
|
|
|
|
PeerName: "kubo-node",
|
|
|
|
|
PeerType: nodeStatus.PeerType,
|
|
|
|
|
Version: nodeStatus.Version,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Log successful status retrieval for debugging
|
|
|
|
|
k.Logger().Debug("IPFS node status retrieved successfully",
|
|
|
|
|
"peer_id", status.PeerId,
|
|
|
|
|
"version", status.Version,
|
|
|
|
|
"peer_type", status.PeerType,
|
|
|
|
|
"connected_peers", nodeStatus.ConnectedPeers,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return &types.QueryIPFSResponse{
|
|
|
|
|
Status: status,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Implement CID query functionality - retrieves data from IPFS using content identifiers
|
|
|
|
|
// Should validate CID format and check if content exists in IPFS
|
|
|
|
|
// Retrieve content metadata without downloading full data
|
|
|
|
|
// Support different CID versions and hash algorithms
|
|
|
|
|
// Include content size, availability, and pin status
|
|
|
|
|
|
|
|
|
|
// CID implements types.QueryServer.
|
|
|
|
|
func (k Querier) CID(
|
|
|
|
|
goCtx context.Context,
|
|
|
|
|
req *types.QueryCIDRequest,
|
|
|
|
|
) (*types.QueryCIDResponse, error) {
|
|
|
|
|
// Validate input
|
|
|
|
|
if req == nil || req.Cid == "" {
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 400, // Bad Request
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate CID format using go-cid library
|
|
|
|
|
_, err := cid.Parse(req.Cid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Debug("Invalid CID format", "cid", req.Cid, "error", err)
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 400, // Bad Request
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get IPFS client with connectivity check
|
|
|
|
|
ipfsClient, err := k.GetIPFSClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Error("IPFS client not available", "error", err)
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 500, // Internal Server Error
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if content exists in IPFS
|
|
|
|
|
exists, err := ipfsClient.Exists(req.Cid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Error("Error checking CID existence", "cid", req.Cid, "error", err)
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 500, // Internal Server Error
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !exists {
|
|
|
|
|
k.Logger().Debug("CID not found in IPFS", "cid", req.Cid)
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 404, // Not Found
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retrieve content from IPFS
|
|
|
|
|
data, err := ipfsClient.Get(req.Cid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Error("Error retrieving content from IPFS", "cid", req.Cid, "error", err)
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 500, // Internal Server Error
|
|
|
|
|
Data: nil,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k.Logger().Debug("Successfully retrieved content from IPFS",
|
|
|
|
|
"cid", req.Cid,
|
|
|
|
|
"size", len(data))
|
|
|
|
|
|
|
|
|
|
return &types.QueryCIDResponse{
|
|
|
|
|
StatusCode: 200, // Success
|
|
|
|
|
Data: data,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncryptedRecord queries a specific encrypted record with automatic decryption
|
|
|
|
|
func (k Querier) EncryptedRecord(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryEncryptedRecordRequest,
|
|
|
|
|
) (*types.QueryEncryptedRecordResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Target == "" {
|
|
|
|
|
return nil, types.ErrTargetDIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.RecordId == "" {
|
|
|
|
|
return nil, types.ErrRecordIDEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Get the record first
|
|
|
|
|
record, err := k.OrmDB.DWNRecordTable().Get(ctx, req.RecordId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if ormerrors.IsNotFound(err) {
|
|
|
|
|
return nil, errors.Wrapf(types.ErrRecordNotFound, "record %s not found", req.RecordId)
|
|
|
|
|
}
|
|
|
|
|
return nil, errors.Wrap(err, "failed to get record")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the record belongs to the target DWN
|
|
|
|
|
if record.Target != req.Target {
|
|
|
|
|
return nil, errors.Wrap(sdkerrors.ErrUnauthorized, "record does not belong to target DWN")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec := types.ConvertAPIRecordToType(record)
|
|
|
|
|
|
|
|
|
|
// Check if the record has encryption metadata
|
|
|
|
|
if record.EncryptionMetadata == nil {
|
|
|
|
|
return &types.QueryEncryptedRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
WasDecrypted: false,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If return_encrypted is true, return the encrypted record without decryption
|
|
|
|
|
if req.ReturnEncrypted {
|
|
|
|
|
return &types.QueryEncryptedRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
EncryptionMetadata: types.ConvertAPIEncryptionMetadataToType(record.EncryptionMetadata),
|
|
|
|
|
WasDecrypted: false,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to decrypt the record data if we have encryption subkeeper
|
|
|
|
|
if k.encryptionSubkeeper != nil && len(rec.Data) > 0 {
|
|
|
|
|
decryptedData, err := k.encryptionSubkeeper.DecryptWithConsensusKey(
|
|
|
|
|
c,
|
|
|
|
|
rec.Data,
|
|
|
|
|
types.ConvertAPIEncryptionMetadataToType(record.EncryptionMetadata),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
k.Logger().Error("Failed to decrypt record data",
|
|
|
|
|
"record_id", req.RecordId,
|
|
|
|
|
"error", err,
|
|
|
|
|
)
|
|
|
|
|
// Return the encrypted record if decryption fails
|
|
|
|
|
return &types.QueryEncryptedRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
EncryptionMetadata: types.ConvertAPIEncryptionMetadataToType(
|
|
|
|
|
record.EncryptionMetadata,
|
|
|
|
|
),
|
|
|
|
|
WasDecrypted: false,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update record with decrypted data
|
|
|
|
|
rec.Data = decryptedData
|
|
|
|
|
return &types.QueryEncryptedRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
EncryptionMetadata: types.ConvertAPIEncryptionMetadataToType(record.EncryptionMetadata),
|
|
|
|
|
WasDecrypted: true,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No encryption subkeeper or no encrypted data
|
|
|
|
|
return &types.QueryEncryptedRecordResponse{
|
|
|
|
|
Record: &rec,
|
|
|
|
|
WasDecrypted: false,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncryptionStatus queries current encryption key state and version
|
|
|
|
|
func (k Querier) EncryptionStatus(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryEncryptionStatusRequest,
|
|
|
|
|
) (*types.QueryEncryptionStatusResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default response with minimal information
|
|
|
|
|
response := &types.QueryEncryptionStatusResponse{
|
|
|
|
|
CurrentKeyVersion: 0,
|
|
|
|
|
ValidatorSet: []string{},
|
|
|
|
|
SingleNodeMode: true,
|
|
|
|
|
LastRotation: 0,
|
|
|
|
|
NextRotation: 0,
|
|
|
|
|
TotalEncryptedRecords: 0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we have encryption subkeeper, get detailed status
|
|
|
|
|
if k.encryptionSubkeeper != nil {
|
|
|
|
|
// Get current key version
|
|
|
|
|
response.CurrentKeyVersion = k.encryptionSubkeeper.GetCurrentKeyVersion(c)
|
|
|
|
|
|
|
|
|
|
// Check if single node mode
|
|
|
|
|
response.SingleNodeMode = k.encryptionSubkeeper.isSingleNodeMode(c)
|
|
|
|
|
|
|
|
|
|
// Get validator set
|
|
|
|
|
validators, err := k.encryptionSubkeeper.getActiveValidators(c)
|
|
|
|
|
if err == nil {
|
|
|
|
|
validatorAddrs := make([]string, len(validators))
|
|
|
|
|
for i, v := range validators {
|
|
|
|
|
validatorAddrs[i] = fmt.Sprintf("%v", v)
|
|
|
|
|
}
|
|
|
|
|
response.ValidatorSet = validatorAddrs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get stored key state for rotation timestamps
|
|
|
|
|
keyState, keyErr := k.encryptionSubkeeper.getStoredKeyState(c)
|
|
|
|
|
if keyErr == nil {
|
|
|
|
|
response.LastRotation = keyState.LastRotation
|
|
|
|
|
response.NextRotation = keyState.NextRotation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get encryption statistics from the encryption subkeeper
|
|
|
|
|
stats, statsErr := k.encryptionSubkeeper.GetEncryptionStats(c)
|
|
|
|
|
if statsErr == nil {
|
|
|
|
|
// Safely convert int64 to uint64
|
|
|
|
|
if stats.TotalEncryptedRecords < 0 {
|
|
|
|
|
response.TotalEncryptedRecords = 0
|
|
|
|
|
} else {
|
|
|
|
|
response.TotalEncryptedRecords = uint64(stats.TotalEncryptedRecords)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k.Logger().Debug("Retrieved encryption status",
|
|
|
|
|
"key_version", response.CurrentKeyVersion,
|
|
|
|
|
"single_node_mode", response.SingleNodeMode,
|
|
|
|
|
"validator_count", len(response.ValidatorSet),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VRFContributions lists VRF contributions for current consensus round
|
|
|
|
|
func (k Querier) VRFContributions(
|
|
|
|
|
c context.Context,
|
|
|
|
|
req *types.QueryVRFContributionsRequest,
|
|
|
|
|
) (*types.QueryVRFContributionsResponse, error) {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return nil, types.ErrRequestCannotBeNil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := sdk.UnwrapSDKContext(c)
|
|
|
|
|
|
|
|
|
|
// Query VRF contributions from the database using filters
|
|
|
|
|
var indexKey apiv1.VRFContributionIndexKey
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case req.ValidatorAddress != "" && req.BlockHeight > 0:
|
|
|
|
|
// Filter by both validator address and block height
|
|
|
|
|
indexKey = apiv1.VRFContributionValidatorAddressBlockHeightIndexKey{}.
|
|
|
|
|
WithValidatorAddressBlockHeight(req.ValidatorAddress, req.BlockHeight)
|
|
|
|
|
case req.ValidatorAddress != "":
|
|
|
|
|
// Filter by validator address only
|
|
|
|
|
indexKey = apiv1.VRFContributionValidatorAddressBlockHeightIndexKey{}.
|
|
|
|
|
WithValidatorAddress(req.ValidatorAddress)
|
|
|
|
|
case req.BlockHeight > 0:
|
|
|
|
|
// Filter by block height only
|
|
|
|
|
indexKey = apiv1.VRFContributionBlockHeightIndexKey{}.
|
|
|
|
|
WithBlockHeight(req.BlockHeight)
|
|
|
|
|
default:
|
|
|
|
|
// No filters, list all contributions
|
|
|
|
|
indexKey = apiv1.VRFContributionPrimaryKey{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query with pagination
|
|
|
|
|
pageReq := req.Pagination
|
|
|
|
|
if pageReq == nil {
|
|
|
|
|
pageReq = &query.PageRequest{Limit: 100}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contributions := []types.VRFContribution{}
|
|
|
|
|
|
|
|
|
|
iter, err := k.OrmDB.VRFContributionTable().List(c, indexKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to list VRF contributions: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defer iter.Close()
|
|
|
|
|
|
|
|
|
|
count := uint64(0)
|
|
|
|
|
offset := pageReq.Offset
|
|
|
|
|
limit := pageReq.Limit
|
|
|
|
|
|
|
|
|
|
for iter.Next() {
|
|
|
|
|
contrib, iterErr := iter.Value()
|
|
|
|
|
if iterErr != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
|
|
// Handle pagination
|
|
|
|
|
if count <= offset {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if uint64(len(contributions)) >= limit {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert from API type to types
|
|
|
|
|
contributions = append(contributions, types.VRFContribution{
|
|
|
|
|
ValidatorAddress: contrib.ValidatorAddress,
|
|
|
|
|
Randomness: contrib.Randomness,
|
|
|
|
|
Proof: contrib.Proof,
|
|
|
|
|
BlockHeight: contrib.BlockHeight,
|
|
|
|
|
Timestamp: contrib.Timestamp,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current consensus round information from database
|
|
|
|
|
blockHeight := ctx.BlockHeight()
|
|
|
|
|
var roundNumber uint64
|
|
|
|
|
if blockHeight > 0 {
|
|
|
|
|
// Safe conversion: blockHeight is positive int64, division result fits in uint64
|
|
|
|
|
roundNumber = uint64(blockHeight) / 100
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentRound *types.VRFConsensusRound
|
|
|
|
|
|
|
|
|
|
// Try to get stored consensus round from database
|
|
|
|
|
storedRound, roundErr := k.OrmDB.VRFConsensusRoundTable().Get(c, roundNumber)
|
|
|
|
|
if roundErr == nil {
|
|
|
|
|
// Convert from API type
|
|
|
|
|
currentRound = &types.VRFConsensusRound{
|
|
|
|
|
RoundNumber: storedRound.RoundNumber,
|
|
|
|
|
RequiredContributions: storedRound.RequiredContributions,
|
|
|
|
|
ReceivedContributions: storedRound.ReceivedContributions,
|
|
|
|
|
Status: storedRound.Status,
|
|
|
|
|
ExpiryHeight: storedRound.ExpiryHeight,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Create new round information if not found in database
|
|
|
|
|
// Safely convert contribution count to uint32
|
|
|
|
|
var receivedContributions uint32
|
|
|
|
|
contributionCount := len(contributions)
|
|
|
|
|
switch {
|
|
|
|
|
case contributionCount > 4294967295: // Max uint32
|
|
|
|
|
receivedContributions = 4294967295
|
|
|
|
|
case contributionCount < 0:
|
|
|
|
|
receivedContributions = 0
|
|
|
|
|
default:
|
|
|
|
|
receivedContributions = uint32(contributionCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentRound = &types.VRFConsensusRound{
|
|
|
|
|
RoundNumber: roundNumber,
|
|
|
|
|
RequiredContributions: 1,
|
|
|
|
|
ReceivedContributions: receivedContributions,
|
|
|
|
|
Status: "waiting_for_contributions",
|
|
|
|
|
ExpiryHeight: ctx.BlockHeight() + 100,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate required contributions based on active validators
|
|
|
|
|
if k.encryptionSubkeeper != nil {
|
|
|
|
|
validators, validatorErr := k.encryptionSubkeeper.getActiveValidators(c)
|
|
|
|
|
if validatorErr == nil {
|
|
|
|
|
validatorCount := len(validators)
|
|
|
|
|
if validatorCount > 1 {
|
|
|
|
|
// Byzantine fault tolerance: need 2/3 + 1 contributions
|
|
|
|
|
bftThreshold := (validatorCount * 2 / 3) + 1
|
|
|
|
|
if bftThreshold > 0 && bftThreshold <= int(^uint32(0)) {
|
|
|
|
|
currentRound.RequiredContributions = uint32(bftThreshold)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update status based on single node mode
|
|
|
|
|
if k.encryptionSubkeeper.isSingleNodeMode(c) {
|
|
|
|
|
currentRound.Status = "single_node_mode"
|
|
|
|
|
currentRound.ReceivedContributions = 1
|
|
|
|
|
} else if currentRound.ReceivedContributions >= currentRound.RequiredContributions {
|
|
|
|
|
currentRound.Status = "completed"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageRes := &query.PageResponse{
|
|
|
|
|
Total: uint64(len(contributions)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k.Logger().Debug("Retrieved VRF contributions",
|
|
|
|
|
"contributions_count", len(contributions),
|
|
|
|
|
"round_number", currentRound.RoundNumber,
|
|
|
|
|
"required_contributions", currentRound.RequiredContributions,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return &types.QueryVRFContributionsResponse{
|
|
|
|
|
Contributions: contributions,
|
|
|
|
|
CurrentRound: currentRound,
|
|
|
|
|
Pagination: pageRes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|