mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
537 lines
15 KiB
Go
537 lines
15 KiB
Go
// Package keeper provides the DWN module keeper implementation.
|
|
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
|
|
|
feegrantkeeper "cosmossdk.io/x/feegrant/keeper"
|
|
|
|
"cosmossdk.io/collections"
|
|
storetypes "cosmossdk.io/core/store"
|
|
"cosmossdk.io/errors"
|
|
"cosmossdk.io/log"
|
|
"cosmossdk.io/orm/model/ormdb"
|
|
|
|
apiv1 "github.com/sonr-io/sonr/api/dwn/v1"
|
|
sonrcontext "github.com/sonr-io/sonr/app/context"
|
|
"github.com/sonr-io/sonr/crypto/mpc"
|
|
"github.com/sonr-io/sonr/crypto/vrf"
|
|
"github.com/sonr-io/sonr/types/ipfs"
|
|
didtypes "github.com/sonr-io/sonr/x/did/types"
|
|
"github.com/sonr-io/sonr/x/dwn/types"
|
|
)
|
|
|
|
type Keeper struct {
|
|
cdc codec.BinaryCodec
|
|
|
|
logger log.Logger
|
|
|
|
// state management
|
|
Schema collections.Schema
|
|
Params collections.Item[types.Params]
|
|
OrmDB apiv1.StateStore
|
|
|
|
// SDK keepers for wallet operations
|
|
accountKeeper authkeeper.AccountKeeper
|
|
bankKeeper bankkeeper.Keeper
|
|
feegrantKeeper feegrantkeeper.Keeper
|
|
stakingKeeper *stakingkeeper.Keeper
|
|
didKeeper types.DIDKeeper
|
|
serviceKeeper types.ServiceKeeper
|
|
|
|
// client context for transaction building
|
|
clientCtx client.Context
|
|
|
|
// vault client for enclave operations
|
|
ipfsClient ipfs.IPFSClient
|
|
// vaultClient vault.VaultClient
|
|
|
|
// encryption subkeeper for consensus-based encryption
|
|
encryptionSubkeeper *EncryptionSubkeeper
|
|
|
|
// UCAN permission validator for DWN operations
|
|
permissionValidator *PermissionValidator
|
|
|
|
authority string
|
|
|
|
vrfPrivateKey vrf.PrivateKey
|
|
vrfPublicKey vrf.PublicKey
|
|
}
|
|
|
|
// NewKeeper creates a new Keeper instance
|
|
func NewKeeper(
|
|
cdc codec.BinaryCodec,
|
|
storeService storetypes.KVStoreService,
|
|
logger log.Logger,
|
|
authority string,
|
|
accountKeeper authkeeper.AccountKeeper,
|
|
bankKeeper bankkeeper.Keeper,
|
|
feegrantKeeper feegrantkeeper.Keeper,
|
|
stakingKeeper *stakingkeeper.Keeper,
|
|
didKeeper types.DIDKeeper,
|
|
serviceKeeper types.ServiceKeeper,
|
|
clientCtx client.Context,
|
|
) Keeper {
|
|
logger = logger.With(log.ModuleKey, "x/"+types.ModuleName)
|
|
|
|
sb := collections.NewSchemaBuilder(storeService)
|
|
|
|
if authority == "" {
|
|
authority = authtypes.NewModuleAddress(govtypes.ModuleName).String()
|
|
}
|
|
|
|
db, err := ormdb.NewModuleDB(
|
|
&types.ORMModuleSchema,
|
|
ormdb.ModuleDBOptions{KVStoreService: storeService},
|
|
)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
store, err := apiv1.NewStateStore(db)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
k := Keeper{
|
|
cdc: cdc,
|
|
logger: logger,
|
|
|
|
Params: collections.NewItem(
|
|
sb,
|
|
types.ParamsKey,
|
|
"params",
|
|
codec.CollValue[types.Params](cdc),
|
|
),
|
|
OrmDB: store,
|
|
|
|
accountKeeper: accountKeeper,
|
|
bankKeeper: bankKeeper,
|
|
feegrantKeeper: feegrantKeeper,
|
|
didKeeper: didKeeper,
|
|
stakingKeeper: stakingKeeper,
|
|
serviceKeeper: serviceKeeper,
|
|
|
|
clientCtx: clientCtx,
|
|
authority: authority,
|
|
}
|
|
|
|
schema, err := sb.Build()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
k.Schema = schema
|
|
|
|
// Load VRF keys from global context if available
|
|
if errB := k.loadVRFKeysFromContext(); errB != nil {
|
|
logger.Warn("Failed to load VRF keys from context", "error", err)
|
|
// Continue without VRF keys - they can be loaded later
|
|
}
|
|
|
|
// Initialize IPFS client
|
|
ipfsClient, err := ipfs.GetClient()
|
|
if err != nil {
|
|
logger.Error(
|
|
"Failed to initialize IPFS client",
|
|
"error",
|
|
types.ErrIPFSClientNotAvailable,
|
|
)
|
|
// Continue without IPFS client - this allows the keeper to still function
|
|
// but IPFS operations will fail gracefully
|
|
} else {
|
|
k.ipfsClient = ipfsClient
|
|
}
|
|
|
|
// Initialize encryption subkeeper
|
|
k.encryptionSubkeeper = NewEncryptionSubkeeper(&k)
|
|
|
|
// Initialize UCAN permission validator
|
|
k.permissionValidator = NewPermissionValidator(didKeeper)
|
|
|
|
return k
|
|
}
|
|
|
|
func (k Keeper) Logger() log.Logger {
|
|
return k.logger
|
|
}
|
|
|
|
// GetEncryptionSubkeeper returns the encryption subkeeper
|
|
func (k Keeper) GetEncryptionSubkeeper() *EncryptionSubkeeper {
|
|
return k.encryptionSubkeeper
|
|
}
|
|
|
|
// GetPermissionValidator returns the UCAN permission validator
|
|
func (k Keeper) GetPermissionValidator() *PermissionValidator {
|
|
return k.permissionValidator
|
|
}
|
|
|
|
// CheckAndPerformKeyRotation checks if key rotation is due and performs it if needed
|
|
func (k Keeper) CheckAndPerformKeyRotation(ctx context.Context) error {
|
|
return k.encryptionSubkeeper.CheckAndPerformRotation(ctx)
|
|
}
|
|
|
|
// ShouldEncryptRecord determines if a record should be encrypted based on protocol/schema
|
|
func (k Keeper) ShouldEncryptRecord(ctx context.Context, protocol, schema string) (bool, error) {
|
|
params, err := k.Params.Get(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Check if encryption is globally enabled
|
|
if !params.EncryptionEnabled {
|
|
return false, nil
|
|
}
|
|
|
|
// Check if protocol requires encryption
|
|
if slices.Contains(params.EncryptedProtocols, protocol) {
|
|
return true, nil
|
|
}
|
|
|
|
// Check if schema requires encryption
|
|
for _, encryptedSchema := range params.EncryptedSchemas {
|
|
if schema == encryptedSchema ||
|
|
(schema != "" && encryptedSchema != "" &&
|
|
len(schema) >= len(encryptedSchema) &&
|
|
schema[:len(encryptedSchema)] == encryptedSchema) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// InitGenesis initializes the module's state from a genesis state.
|
|
func (k *Keeper) InitGenesis(ctx context.Context, data *types.GenesisState) error {
|
|
if err := data.Params.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := k.Params.Set(ctx, data.Params); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Import DWN records
|
|
for _, record := range data.Records {
|
|
// Convert to API type
|
|
apiRecord := &apiv1.DWNRecord{
|
|
RecordId: record.RecordId,
|
|
Target: record.Target,
|
|
Authorization: record.Authorization,
|
|
Data: record.Data,
|
|
Protocol: record.Protocol,
|
|
ProtocolPath: record.ProtocolPath,
|
|
Schema: record.Schema,
|
|
ParentId: record.ParentId,
|
|
Published: record.Published,
|
|
Attestation: record.Attestation,
|
|
Encryption: record.Encryption,
|
|
KeyDerivationScheme: record.KeyDerivationScheme,
|
|
CreatedAt: record.CreatedAt,
|
|
UpdatedAt: record.UpdatedAt,
|
|
CreatedHeight: record.CreatedHeight,
|
|
}
|
|
if record.Descriptor_ != nil {
|
|
apiRecord.Descriptor_ = &apiv1.DWNMessageDescriptor{
|
|
InterfaceName: record.Descriptor_.InterfaceName,
|
|
Method: record.Descriptor_.Method,
|
|
MessageTimestamp: record.Descriptor_.MessageTimestamp,
|
|
DataCid: record.Descriptor_.DataCid,
|
|
DataSize: record.Descriptor_.DataSize,
|
|
DataFormat: record.Descriptor_.DataFormat,
|
|
}
|
|
}
|
|
if err := k.OrmDB.DWNRecordTable().Insert(ctx, apiRecord); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Import DWN protocols
|
|
for _, protocol := range data.Protocols {
|
|
// Convert to API type
|
|
apiProtocol := &apiv1.DWNProtocol{
|
|
Target: protocol.Target,
|
|
ProtocolUri: protocol.ProtocolUri,
|
|
Definition: protocol.Definition,
|
|
Published: protocol.Published,
|
|
CreatedAt: protocol.CreatedAt,
|
|
CreatedHeight: protocol.CreatedHeight,
|
|
}
|
|
if err := k.OrmDB.DWNProtocolTable().Insert(ctx, apiProtocol); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Import DWN permissions
|
|
for _, permission := range data.Permissions {
|
|
// Convert to API type
|
|
apiPermission := &apiv1.DWNPermission{
|
|
PermissionId: permission.PermissionId,
|
|
Grantor: permission.Grantor,
|
|
Grantee: permission.Grantee,
|
|
Target: permission.Target,
|
|
InterfaceName: permission.InterfaceName,
|
|
Method: permission.Method,
|
|
Protocol: permission.Protocol,
|
|
RecordId: permission.RecordId,
|
|
Conditions: permission.Conditions,
|
|
ExpiresAt: permission.ExpiresAt,
|
|
CreatedAt: permission.CreatedAt,
|
|
Revoked: permission.Revoked,
|
|
CreatedHeight: permission.CreatedHeight,
|
|
}
|
|
if err := k.OrmDB.DWNPermissionTable().Insert(ctx, apiPermission); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Import vault states
|
|
for _, vault := range data.Vaults {
|
|
// Convert to API type
|
|
apiVault := &apiv1.VaultState{
|
|
VaultId: vault.VaultId,
|
|
Owner: vault.Owner,
|
|
PublicKey: vault.PublicKey,
|
|
CreatedAt: vault.CreatedAt,
|
|
LastRefreshed: vault.LastRefreshed,
|
|
CreatedHeight: vault.CreatedHeight,
|
|
}
|
|
if vault.EnclaveData != nil {
|
|
apiVault.EnclaveData = &apiv1.EnclaveData{
|
|
PrivateData: vault.EnclaveData.PrivateData,
|
|
PublicKey: vault.EnclaveData.PublicKey,
|
|
EnclaveId: vault.EnclaveData.EnclaveId,
|
|
Version: vault.EnclaveData.Version,
|
|
}
|
|
}
|
|
if err := k.OrmDB.VaultStateTable().Insert(ctx, apiVault); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExportGenesis exports the module's state to a genesis state.
|
|
func (k *Keeper) ExportGenesis(ctx context.Context) *types.GenesisState {
|
|
params, err := k.Params.Get(ctx)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
genesis := &types.GenesisState{
|
|
Params: params,
|
|
Records: []types.DWNRecord{},
|
|
Protocols: []types.DWNProtocol{},
|
|
Permissions: []types.DWNPermission{},
|
|
Vaults: []types.VaultState{},
|
|
}
|
|
|
|
// Export DWN records
|
|
recordIter, err := k.OrmDB.DWNRecordTable().List(ctx, apiv1.DWNRecordPrimaryKey{})
|
|
if err == nil {
|
|
defer recordIter.Close()
|
|
for recordIter.Next() {
|
|
record, errB := recordIter.Value()
|
|
if errB == nil {
|
|
genesis.Records = append(genesis.Records, types.ConvertAPIRecordToType(record))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export DWN protocols
|
|
protocolIter, err := k.OrmDB.DWNProtocolTable().List(ctx, apiv1.DWNProtocolPrimaryKey{})
|
|
if err == nil {
|
|
defer protocolIter.Close()
|
|
for protocolIter.Next() {
|
|
protocol, errB := protocolIter.Value()
|
|
if errB == nil {
|
|
genesis.Protocols = append(
|
|
genesis.Protocols,
|
|
types.ConvertAPIProtocolToType(protocol),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export DWN permissions
|
|
permissionIter, err := k.OrmDB.DWNPermissionTable().List(ctx, apiv1.DWNPermissionPrimaryKey{})
|
|
if err == nil {
|
|
defer permissionIter.Close()
|
|
for permissionIter.Next() {
|
|
permission, errB := permissionIter.Value()
|
|
if errB == nil {
|
|
genesis.Permissions = append(
|
|
genesis.Permissions,
|
|
types.ConvertAPIPermissionToType(permission),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export vault states
|
|
vaultIter, err := k.OrmDB.VaultStateTable().List(ctx, apiv1.VaultStatePrimaryKey{})
|
|
if err == nil {
|
|
defer vaultIter.Close()
|
|
for vaultIter.Next() {
|
|
vault, err := vaultIter.Value()
|
|
if err == nil {
|
|
genesis.Vaults = append(genesis.Vaults, types.ConvertAPIVaultToType(vault))
|
|
}
|
|
}
|
|
}
|
|
|
|
return genesis
|
|
}
|
|
|
|
// Vault operation methods that delegate to VaultKeeper
|
|
|
|
// ValidateServiceForProtocol validates that a service is registered for a protocol operation
|
|
func (k Keeper) ValidateServiceForProtocol(ctx context.Context, target, serviceID string) error {
|
|
if serviceID == "" {
|
|
// Allow operations without explicit service registration for backward compatibility
|
|
return nil
|
|
}
|
|
|
|
// Extract domain from target (DID format: did:web:domain)
|
|
var domain string
|
|
if len(target) > 8 && target[:8] == "did:web:" {
|
|
domain = target[8:]
|
|
} else {
|
|
k.Logger().Debug("Target is not a DID:web, skipping service verification", "target", target)
|
|
return nil
|
|
}
|
|
|
|
// Verify service registration
|
|
verified, err := k.serviceKeeper.VerifyServiceRegistration(ctx, serviceID, domain)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to verify service registration")
|
|
}
|
|
|
|
if !verified {
|
|
return errors.Wrapf(
|
|
types.ErrServiceNotVerified,
|
|
"service %s not verified for domain %s",
|
|
serviceID,
|
|
domain,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFeeGrantKeeper returns the underlying fee grant keeper for direct access if needed.
|
|
// This method provides access to the fee grant keeper for advanced operations.
|
|
func (k Keeper) GetFeeGrantKeeper() feegrantkeeper.Keeper {
|
|
return k.feegrantKeeper
|
|
}
|
|
|
|
// loadVRFKeysFromContext loads VRF keys from the global SonrContext
|
|
func (k *Keeper) loadVRFKeysFromContext() error {
|
|
ctx := sonrcontext.GetGlobalSonrContext()
|
|
if ctx == nil {
|
|
return fmt.Errorf("global SonrContext not available")
|
|
}
|
|
|
|
if !ctx.IsInitialized() {
|
|
return fmt.Errorf("SonrContext not initialized")
|
|
}
|
|
|
|
privateKey, err := ctx.GetVRFPrivateKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get VRF private key from context: %w", err)
|
|
}
|
|
|
|
publicKey, err := ctx.GetVRFPublicKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get VRF public key from context: %w", err)
|
|
}
|
|
|
|
k.vrfPrivateKey = privateKey
|
|
k.vrfPublicKey = publicKey
|
|
|
|
k.logger.Info("VRF keys loaded from SonrContext",
|
|
"private_key_size", len(k.vrfPrivateKey),
|
|
"public_key_size", len(k.vrfPublicKey),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// GetVRFKeys returns the loaded VRF keypair
|
|
func (k Keeper) GetVRFKeys() (vrf.PrivateKey, vrf.PublicKey, error) {
|
|
if len(k.vrfPrivateKey) == 0 || len(k.vrfPublicKey) == 0 {
|
|
// Try to load from context if not already loaded
|
|
if err := k.loadVRFKeysFromContext(); err != nil {
|
|
return nil, nil, fmt.Errorf("VRF keys not loaded: %w\n"+
|
|
"To fix this issue:\n"+
|
|
" 1. Run 'snrd init <moniker>' to generate VRF keys for your node\n"+
|
|
" 2. Or disable encryption in DWN module params if not needed\n"+
|
|
" 3. For existing nodes, VRF keys should be in ~/.sonr/vrf_secret.key", err)
|
|
}
|
|
}
|
|
|
|
return k.vrfPrivateKey, k.vrfPublicKey, nil
|
|
}
|
|
|
|
// ComputeVRF generates VRF output using the keeper's loaded private key
|
|
func (k Keeper) ComputeVRF(input []byte) ([]byte, error) {
|
|
privateKey, _, err := k.GetVRFKeys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get VRF keys: %w", err)
|
|
}
|
|
|
|
if len(input) == 0 {
|
|
return nil, fmt.Errorf("VRF input cannot be empty")
|
|
}
|
|
|
|
return privateKey.Compute(input), nil
|
|
}
|
|
|
|
// CreateVaultForDID creates a vault for a given DID using the WebAssembly enclave plugin
|
|
func (k Keeper) CreateVaultForDID(
|
|
ctx context.Context,
|
|
data *mpc.EnclaveData,
|
|
) (*didtypes.CreateVaultResponse, error) {
|
|
// Input validation
|
|
vaultState, err := k.AddEnclaveDataToIPFS(ctx, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Insert the vault state into the database
|
|
if err := k.OrmDB.VaultStateTable().Insert(ctx, vaultState); err != nil {
|
|
k.logger.Error("Failed to store vault state",
|
|
"vault_id", vaultState.VaultId,
|
|
"error", err,
|
|
)
|
|
return nil, fmt.Errorf("failed to store vault state: %w", err)
|
|
}
|
|
|
|
// Emit typed event
|
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
|
event := &types.EventVaultCreated{
|
|
VaultId: vaultState.VaultId,
|
|
Owner: vaultState.Owner,
|
|
PublicKey: string(vaultState.PublicKey),
|
|
BlockHeight: uint64(sdkCtx.BlockHeight()),
|
|
}
|
|
|
|
if err := sdkCtx.EventManager().EmitTypedEvent(event); err != nil {
|
|
k.logger.With("error", err).Error("Failed to emit EventVaultCreated")
|
|
}
|
|
|
|
return &didtypes.CreateVaultResponse{}, nil
|
|
}
|