Files
sonr/x/dwn/keeper/dwn_records.go
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

286 lines
8.3 KiB
Go

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
}