mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-05 10:51:41 +00:00
@@ -0,0 +1,285 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/errors"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
apiv1 "github.com/sonr-io/sonr/api/dwn/v1"
|
||||
"github.com/sonr-io/sonr/x/dwn/types"
|
||||
)
|
||||
|
||||
// RecordsWrite creates or updates a record in the DWN
|
||||
func (k Keeper) RecordsWrite(
|
||||
ctx context.Context,
|
||||
msg *types.MsgRecordsWrite,
|
||||
) (*types.MsgRecordsWriteResponse, error) {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
// Validate service registration for record operations
|
||||
if msg.Authorization != "" {
|
||||
// Try to extract service ID from authorization
|
||||
var serviceID string
|
||||
if len(msg.Authorization) > 8 && msg.Authorization[:8] == "service:" {
|
||||
serviceID = msg.Authorization[8:]
|
||||
}
|
||||
|
||||
if err := k.ValidateServiceForProtocol(ctx, msg.Target, serviceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note: Service-based authorization handled by ValidateServiceForProtocol above
|
||||
// Legacy UCAN validation is now replaced by service module capabilities
|
||||
}
|
||||
|
||||
// Validate record size against params
|
||||
params, err := k.Params.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uint64(len(msg.Data)) > params.MaxRecordSize {
|
||||
return nil, errors.Wrapf(
|
||||
types.ErrRecordSizeExceeded,
|
||||
"record size %d exceeds max size %d",
|
||||
len(msg.Data),
|
||||
params.MaxRecordSize,
|
||||
)
|
||||
}
|
||||
|
||||
// Determine if record should be encrypted
|
||||
shouldEncrypt, err := k.ShouldEncryptRecord(ctx, msg.Protocol, msg.Schema)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to determine encryption requirement")
|
||||
}
|
||||
|
||||
var recordData []byte
|
||||
var encryptionMetadata *types.EncryptionMetadata
|
||||
var isEncrypted bool
|
||||
|
||||
if shouldEncrypt && k.encryptionSubkeeper != nil {
|
||||
// Encrypt the record data using consensus-derived key
|
||||
encryptedData, errB := k.encryptionSubkeeper.EncryptWithConsensusKey(
|
||||
ctx,
|
||||
msg.Data,
|
||||
msg.Protocol,
|
||||
)
|
||||
if errB != nil {
|
||||
// Log error but fallback to unencrypted storage
|
||||
k.Logger().Error("Failed to encrypt record, storing unencrypted",
|
||||
"error", err,
|
||||
"protocol", msg.Protocol,
|
||||
"schema", msg.Schema,
|
||||
)
|
||||
recordData = msg.Data
|
||||
isEncrypted = false
|
||||
} else {
|
||||
recordData = encryptedData.Ciphertext
|
||||
encryptionMetadata = encryptedData.Metadata
|
||||
isEncrypted = true
|
||||
k.Logger().Info("Record encrypted successfully",
|
||||
"protocol", msg.Protocol,
|
||||
"data_size", len(msg.Data),
|
||||
"encrypted_size", len(recordData),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
recordData = msg.Data
|
||||
isEncrypted = false
|
||||
}
|
||||
|
||||
// Generate record ID from original content hash (not encrypted data)
|
||||
hasher := sha256.New()
|
||||
hasher.Write(msg.Data)
|
||||
hasher.Write([]byte(msg.Target))
|
||||
hasher.Write([]byte(msg.Descriptor_.MessageTimestamp))
|
||||
dataHash := hasher.Sum(nil)
|
||||
recordID := hex.EncodeToString(dataHash)
|
||||
|
||||
// Calculate data CID (simplified - in production use proper IPLD CID)
|
||||
dataCID := "cid:" + hex.EncodeToString(dataHash[:16])
|
||||
|
||||
// Check if record exists
|
||||
existingRecord, err := k.OrmDB.DWNRecordTable().Get(ctx, recordID)
|
||||
if err == nil && existingRecord != nil {
|
||||
// Update existing record
|
||||
existingRecord.Data = recordData // Use potentially encrypted data
|
||||
existingRecord.Descriptor_ = &apiv1.DWNMessageDescriptor{
|
||||
InterfaceName: msg.Descriptor_.InterfaceName,
|
||||
Method: msg.Descriptor_.Method,
|
||||
MessageTimestamp: msg.Descriptor_.MessageTimestamp,
|
||||
DataCid: dataCID,
|
||||
DataSize: int64(len(msg.Data)), // Original data size
|
||||
DataFormat: msg.Descriptor_.DataFormat,
|
||||
}
|
||||
existingRecord.Authorization = msg.Authorization
|
||||
existingRecord.Protocol = msg.Protocol
|
||||
existingRecord.ProtocolPath = msg.ProtocolPath
|
||||
existingRecord.Schema = msg.Schema
|
||||
existingRecord.ParentId = msg.ParentId
|
||||
existingRecord.Published = msg.Published
|
||||
existingRecord.Encryption = msg.Encryption
|
||||
existingRecord.Attestation = msg.Attestation
|
||||
existingRecord.UpdatedAt = time.Now().Unix()
|
||||
existingRecord.IsEncrypted = isEncrypted
|
||||
if encryptionMetadata != nil {
|
||||
existingRecord.EncryptionMetadata = encryptionMetadata.ToAPIEncryptionMetadata()
|
||||
}
|
||||
|
||||
if err := k.OrmDB.DWNRecordTable().Update(ctx, existingRecord); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to update record")
|
||||
}
|
||||
|
||||
k.Logger().Info("Updated DWN record",
|
||||
"record_id", recordID,
|
||||
"target", msg.Target,
|
||||
"encrypted", isEncrypted,
|
||||
)
|
||||
|
||||
// Emit typed event
|
||||
event := &types.EventRecordWritten{
|
||||
RecordId: recordID,
|
||||
Target: msg.Target,
|
||||
Protocol: msg.Protocol,
|
||||
Schema: msg.Schema,
|
||||
DataCid: dataCID,
|
||||
DataSize: uint64(len(msg.Data)),
|
||||
Encrypted: isEncrypted,
|
||||
BlockHeight: uint64(sdkCtx.BlockHeight()),
|
||||
}
|
||||
|
||||
if err := sdkCtx.EventManager().EmitTypedEvent(event); err != nil {
|
||||
k.Logger().With("error", err).Error("Failed to emit EventRecordWritten")
|
||||
}
|
||||
} else {
|
||||
// Create new record
|
||||
record := &apiv1.DWNRecord{
|
||||
RecordId: recordID,
|
||||
Target: msg.Target,
|
||||
Descriptor_: &apiv1.DWNMessageDescriptor{
|
||||
InterfaceName: msg.Descriptor_.InterfaceName,
|
||||
Method: msg.Descriptor_.Method,
|
||||
MessageTimestamp: msg.Descriptor_.MessageTimestamp,
|
||||
DataCid: dataCID,
|
||||
DataSize: int64(len(msg.Data)), // Original data size
|
||||
DataFormat: msg.Descriptor_.DataFormat,
|
||||
},
|
||||
Authorization: msg.Authorization,
|
||||
Data: recordData, // Use potentially encrypted data
|
||||
Protocol: msg.Protocol,
|
||||
ProtocolPath: msg.ProtocolPath,
|
||||
Schema: msg.Schema,
|
||||
ParentId: msg.ParentId,
|
||||
Published: msg.Published,
|
||||
Attestation: msg.Attestation,
|
||||
Encryption: msg.Encryption,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
CreatedHeight: sdkCtx.BlockHeight(),
|
||||
IsEncrypted: isEncrypted,
|
||||
}
|
||||
|
||||
if encryptionMetadata != nil {
|
||||
record.EncryptionMetadata = encryptionMetadata.ToAPIEncryptionMetadata()
|
||||
}
|
||||
|
||||
if err := k.OrmDB.DWNRecordTable().Insert(ctx, record); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to insert record")
|
||||
}
|
||||
|
||||
k.Logger().Info("Created DWN record",
|
||||
"record_id", recordID,
|
||||
"target", msg.Target,
|
||||
"encrypted", isEncrypted,
|
||||
)
|
||||
|
||||
// Emit typed event
|
||||
event := &types.EventRecordWritten{
|
||||
RecordId: recordID,
|
||||
Target: msg.Target,
|
||||
Protocol: msg.Protocol,
|
||||
Schema: msg.Schema,
|
||||
DataCid: dataCID,
|
||||
DataSize: uint64(len(msg.Data)),
|
||||
Encrypted: isEncrypted,
|
||||
BlockHeight: uint64(sdkCtx.BlockHeight()),
|
||||
}
|
||||
|
||||
if err := sdkCtx.EventManager().EmitTypedEvent(event); err != nil {
|
||||
k.Logger().With("error", err).Error("Failed to emit EventRecordWritten")
|
||||
}
|
||||
}
|
||||
|
||||
return &types.MsgRecordsWriteResponse{
|
||||
RecordId: recordID,
|
||||
DataCid: dataCID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RecordsDelete deletes a record from the DWN
|
||||
func (k Keeper) RecordsDelete(
|
||||
ctx context.Context,
|
||||
msg *types.MsgRecordsDelete,
|
||||
) (*types.MsgRecordsDeleteResponse, error) {
|
||||
// Validate UCAN authorization if provided
|
||||
|
||||
// Get the record
|
||||
record, err := k.OrmDB.DWNRecordTable().Get(ctx, msg.RecordId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(types.ErrRecordNotFound, "record %s not found", msg.RecordId)
|
||||
}
|
||||
|
||||
// Verify ownership/permission
|
||||
if record.Target != msg.Target {
|
||||
return nil, errors.Wrapf(types.ErrRecordPermission, "target mismatch")
|
||||
}
|
||||
|
||||
deletedCount := int32(1)
|
||||
|
||||
// Handle pruning of child records if requested
|
||||
if msg.Prune && msg.RecordId != "" {
|
||||
// Find and delete all child records
|
||||
indexKey := apiv1.DWNRecordParentIdIndexKey{}.WithParentId(msg.RecordId)
|
||||
iter, err := k.OrmDB.DWNRecordTable().List(ctx, indexKey)
|
||||
if err == nil {
|
||||
defer iter.Close()
|
||||
for iter.Next() {
|
||||
childRecord, err := iter.Value()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err := k.OrmDB.DWNRecordTable().Delete(ctx, childRecord); err == nil {
|
||||
deletedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the record
|
||||
if err := k.OrmDB.DWNRecordTable().Delete(ctx, record); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to delete record")
|
||||
}
|
||||
|
||||
k.Logger().Info("Deleted DWN record", "record_id", msg.RecordId, "pruned_count", deletedCount)
|
||||
|
||||
// Emit typed event
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
event := &types.EventRecordDeleted{
|
||||
RecordId: msg.RecordId,
|
||||
Target: msg.Target,
|
||||
Deleter: msg.Target, // The deleter is the target in this case
|
||||
BlockHeight: uint64(sdkCtx.BlockHeight()),
|
||||
}
|
||||
|
||||
if err := sdkCtx.EventManager().EmitTypedEvent(event); err != nil {
|
||||
k.Logger().With("error", err).Error("Failed to emit EventRecordDeleted")
|
||||
}
|
||||
|
||||
return &types.MsgRecordsDeleteResponse{
|
||||
Success: true,
|
||||
DeletedCount: deletedCount,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user