mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
286 lines
8.3 KiB
Go
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
|
|
}
|