package state import ( "errors" "math/big" "git.marketally.com/tutus-one/tutus-chain/pkg/util" "git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) var errPalamInvalidStackItem = errors.New("invalid stack item") // FlowStatus represents the status of a transaction flow. type FlowStatus uint8 const ( FlowStatusActive FlowStatus = 0 FlowStatusArchived FlowStatus = 1 FlowStatusDisputed FlowStatus = 2 ) // DeclassifyStatus represents the status of a declassification request. type DeclassifyStatus uint8 const ( DeclassifyPending DeclassifyStatus = 0 DeclassifyApproved DeclassifyStatus = 1 DeclassifyDenied DeclassifyStatus = 2 DeclassifyExpired DeclassifyStatus = 3 ) // PalamRole represents roles for transparency access. type PalamRole uint8 const ( PalamRoleConsumer PalamRole = 0 PalamRoleMerchant PalamRole = 1 PalamRoleDistributor PalamRole = 2 PalamRoleProducer PalamRole = 3 PalamRoleNGO PalamRole = 4 PalamRoleAuditor PalamRole = 5 ) // AccessType represents types of data access. type AccessType uint8 const ( AccessTypeView AccessType = 0 AccessTypeDeclassify AccessType = 1 AccessTypeAttach AccessType = 2 ) // Flow represents a transaction record with encrypted role-based payloads. type Flow struct { FlowID util.Uint256 // Unique identifier (hash of contents) Bucket string // Time bucket (e.g., "2025-01-15T14:00:00Z") Tag string // Category: COFFEE, DONATION, SUPPLY, etc. Amount uint64 // Value in smallest unit Timestamp uint32 // Block height when recorded Creator util.Uint160 // Who created the flow // Encrypted payloads - each role has its own encrypted view ConsumerData []byte // Encrypted with Consumer key MerchantData []byte // Encrypted with Merchant key DistributorData []byte // Encrypted with Distributor key ProducerData []byte // Encrypted with Producer key NGOData []byte // Encrypted with NGO key AuditorData []byte // Encrypted with Auditor key (requires declassify) // Participants (script hashes) Participants []util.Uint160 // Chain of custody PreviousFlowID util.Uint256 // Links flows in a supply chain Status FlowStatus } // ToStackItem converts Flow to a stack item. func (f *Flow) ToStackItem() stackitem.Item { participants := make([]stackitem.Item, len(f.Participants)) for i, p := range f.Participants { participants[i] = stackitem.NewByteArray(p.BytesBE()) } return stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(f.FlowID.BytesBE()), stackitem.NewByteArray([]byte(f.Bucket)), stackitem.NewByteArray([]byte(f.Tag)), stackitem.NewBigInteger(big.NewInt(int64(f.Amount))), stackitem.NewBigInteger(big.NewInt(int64(f.Timestamp))), stackitem.NewByteArray(f.Creator.BytesBE()), stackitem.NewByteArray(f.ConsumerData), stackitem.NewByteArray(f.MerchantData), stackitem.NewByteArray(f.DistributorData), stackitem.NewByteArray(f.ProducerData), stackitem.NewByteArray(f.NGOData), stackitem.NewByteArray(f.AuditorData), stackitem.NewArray(participants), stackitem.NewByteArray(f.PreviousFlowID.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(f.Status))), }) } // FromStackItem populates Flow from a stack item. func (f *Flow) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 15 { return errPalamInvalidStackItem } flowIDBytes, err := arr[0].TryBytes() if err != nil { return err } f.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes) if err != nil { return err } bucketBytes, err := arr[1].TryBytes() if err != nil { return err } f.Bucket = string(bucketBytes) tagBytes, err := arr[2].TryBytes() if err != nil { return err } f.Tag = string(tagBytes) amount, err := arr[3].TryInteger() if err != nil { return err } f.Amount = amount.Uint64() timestamp, err := arr[4].TryInteger() if err != nil { return err } f.Timestamp = uint32(timestamp.Uint64()) creatorBytes, err := arr[5].TryBytes() if err != nil { return err } f.Creator, err = util.Uint160DecodeBytesBE(creatorBytes) if err != nil { return err } f.ConsumerData, err = arr[6].TryBytes() if err != nil { return err } f.MerchantData, err = arr[7].TryBytes() if err != nil { return err } f.DistributorData, err = arr[8].TryBytes() if err != nil { return err } f.ProducerData, err = arr[9].TryBytes() if err != nil { return err } f.NGOData, err = arr[10].TryBytes() if err != nil { return err } f.AuditorData, err = arr[11].TryBytes() if err != nil { return err } participantsArr, ok := arr[12].Value().([]stackitem.Item) if !ok { return errPalamInvalidStackItem } f.Participants = make([]util.Uint160, len(participantsArr)) for i, p := range participantsArr { pBytes, err := p.TryBytes() if err != nil { return err } f.Participants[i], err = util.Uint160DecodeBytesBE(pBytes) if err != nil { return err } } prevFlowBytes, err := arr[13].TryBytes() if err != nil { return err } if len(prevFlowBytes) > 0 { f.PreviousFlowID, err = util.Uint256DecodeBytesBE(prevFlowBytes) if err != nil { return err } } status, err := arr[14].TryInteger() if err != nil { return err } f.Status = FlowStatus(status.Uint64()) return nil } // DeclassifyRequest represents a request for elevated access to flow data. type DeclassifyRequest struct { RequestID uint64 // Unique identifier FlowID util.Uint256 // Target flow CaseID string // Legal case reference Reason string // Justification for access Requester util.Uint160 // Script hash of requester RequesterRole PalamRole // Must be Auditor // Approval tracking RequiredApprovals uint32 // e.g., 2 of 3 Approvals []util.Uint160 // Script hashes of approvers ApprovalTimes []uint32 // Block heights of approvals // Status Status DeclassifyStatus CreatedAt uint32 // Block height when created ExpiresAt uint32 // Request expires if not approved GrantedAt uint32 // When access was granted } // ToStackItem converts DeclassifyRequest to a stack item. func (d *DeclassifyRequest) ToStackItem() stackitem.Item { approvals := make([]stackitem.Item, len(d.Approvals)) for i, a := range d.Approvals { approvals[i] = stackitem.NewByteArray(a.BytesBE()) } approvalTimes := make([]stackitem.Item, len(d.ApprovalTimes)) for i, t := range d.ApprovalTimes { approvalTimes[i] = stackitem.NewBigInteger(big.NewInt(int64(t))) } return stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(d.RequestID))), stackitem.NewByteArray(d.FlowID.BytesBE()), stackitem.NewByteArray([]byte(d.CaseID)), stackitem.NewByteArray([]byte(d.Reason)), stackitem.NewByteArray(d.Requester.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(d.RequesterRole))), stackitem.NewBigInteger(big.NewInt(int64(d.RequiredApprovals))), stackitem.NewArray(approvals), stackitem.NewArray(approvalTimes), stackitem.NewBigInteger(big.NewInt(int64(d.Status))), stackitem.NewBigInteger(big.NewInt(int64(d.CreatedAt))), stackitem.NewBigInteger(big.NewInt(int64(d.ExpiresAt))), stackitem.NewBigInteger(big.NewInt(int64(d.GrantedAt))), }) } // FromStackItem populates DeclassifyRequest from a stack item. func (d *DeclassifyRequest) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 13 { return errPalamInvalidStackItem } requestID, err := arr[0].TryInteger() if err != nil { return err } d.RequestID = requestID.Uint64() flowIDBytes, err := arr[1].TryBytes() if err != nil { return err } d.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes) if err != nil { return err } caseIDBytes, err := arr[2].TryBytes() if err != nil { return err } d.CaseID = string(caseIDBytes) reasonBytes, err := arr[3].TryBytes() if err != nil { return err } d.Reason = string(reasonBytes) requesterBytes, err := arr[4].TryBytes() if err != nil { return err } d.Requester, err = util.Uint160DecodeBytesBE(requesterBytes) if err != nil { return err } role, err := arr[5].TryInteger() if err != nil { return err } d.RequesterRole = PalamRole(role.Uint64()) required, err := arr[6].TryInteger() if err != nil { return err } d.RequiredApprovals = uint32(required.Uint64()) approvalsArr, ok := arr[7].Value().([]stackitem.Item) if !ok { return errPalamInvalidStackItem } d.Approvals = make([]util.Uint160, len(approvalsArr)) for i, a := range approvalsArr { aBytes, err := a.TryBytes() if err != nil { return err } d.Approvals[i], err = util.Uint160DecodeBytesBE(aBytes) if err != nil { return err } } timesArr, ok := arr[8].Value().([]stackitem.Item) if !ok { return errPalamInvalidStackItem } d.ApprovalTimes = make([]uint32, len(timesArr)) for i, t := range timesArr { tInt, err := t.TryInteger() if err != nil { return err } d.ApprovalTimes[i] = uint32(tInt.Uint64()) } status, err := arr[9].TryInteger() if err != nil { return err } d.Status = DeclassifyStatus(status.Uint64()) createdAt, err := arr[10].TryInteger() if err != nil { return err } d.CreatedAt = uint32(createdAt.Uint64()) expiresAt, err := arr[11].TryInteger() if err != nil { return err } d.ExpiresAt = uint32(expiresAt.Uint64()) grantedAt, err := arr[12].TryInteger() if err != nil { return err } d.GrantedAt = uint32(grantedAt.Uint64()) return nil } // AccessLog represents a record of data access. type AccessLog struct { LogID uint64 // Unique identifier FlowID util.Uint256 // Which flow was accessed Accessor util.Uint160 // Who accessed AccessType AccessType // Type of access Timestamp uint32 // Block height Details string // Additional context } // ToStackItem converts AccessLog to a stack item. func (a *AccessLog) ToStackItem() stackitem.Item { return stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(a.LogID))), stackitem.NewByteArray(a.FlowID.BytesBE()), stackitem.NewByteArray(a.Accessor.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(a.AccessType))), stackitem.NewBigInteger(big.NewInt(int64(a.Timestamp))), stackitem.NewByteArray([]byte(a.Details)), }) } // FromStackItem populates AccessLog from a stack item. func (a *AccessLog) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 6 { return errPalamInvalidStackItem } logID, err := arr[0].TryInteger() if err != nil { return err } a.LogID = logID.Uint64() flowIDBytes, err := arr[1].TryBytes() if err != nil { return err } a.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes) if err != nil { return err } accessorBytes, err := arr[2].TryBytes() if err != nil { return err } a.Accessor, err = util.Uint160DecodeBytesBE(accessorBytes) if err != nil { return err } accessType, err := arr[3].TryInteger() if err != nil { return err } a.AccessType = AccessType(accessType.Uint64()) timestamp, err := arr[4].TryInteger() if err != nil { return err } a.Timestamp = uint32(timestamp.Uint64()) detailsBytes, err := arr[5].TryBytes() if err != nil { return err } a.Details = string(detailsBytes) return nil } // FlowAttachment represents additional data attached to a flow. type FlowAttachment struct { AttachmentID uint64 // Unique identifier FlowID util.Uint256 // Parent flow AttachmentType string // Type of attachment EncryptedData []byte // Encrypted content Attacher util.Uint160 // Who attached AttachedAt uint32 // Block height } // ToStackItem converts FlowAttachment to a stack item. func (fa *FlowAttachment) ToStackItem() stackitem.Item { return stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(fa.AttachmentID))), stackitem.NewByteArray(fa.FlowID.BytesBE()), stackitem.NewByteArray([]byte(fa.AttachmentType)), stackitem.NewByteArray(fa.EncryptedData), stackitem.NewByteArray(fa.Attacher.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(fa.AttachedAt))), }) } // FromStackItem populates FlowAttachment from a stack item. func (fa *FlowAttachment) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 6 { return errPalamInvalidStackItem } attachmentID, err := arr[0].TryInteger() if err != nil { return err } fa.AttachmentID = attachmentID.Uint64() flowIDBytes, err := arr[1].TryBytes() if err != nil { return err } fa.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes) if err != nil { return err } typeBytes, err := arr[2].TryBytes() if err != nil { return err } fa.AttachmentType = string(typeBytes) fa.EncryptedData, err = arr[3].TryBytes() if err != nil { return err } attacherBytes, err := arr[4].TryBytes() if err != nil { return err } fa.Attacher, err = util.Uint160DecodeBytesBE(attacherBytes) if err != nil { return err } attachedAt, err := arr[5].TryInteger() if err != nil { return err } fa.AttachedAt = uint32(attachedAt.Uint64()) return nil } // RolePermissions defines what each role can do. type RolePermissions struct { CanViewAggregate bool // See totals without identities CanViewIdentities bool // See participant identities CanViewDocuments bool // See attached documents CanAttachData bool // Add metadata to flows CanRequestDeclassify bool // Initiate declassification CanApproveDeclassify bool // Vote on declassification requests } // ToStackItem converts RolePermissions to a stack item. func (rp *RolePermissions) ToStackItem() stackitem.Item { return stackitem.NewArray([]stackitem.Item{ stackitem.NewBool(rp.CanViewAggregate), stackitem.NewBool(rp.CanViewIdentities), stackitem.NewBool(rp.CanViewDocuments), stackitem.NewBool(rp.CanAttachData), stackitem.NewBool(rp.CanRequestDeclassify), stackitem.NewBool(rp.CanApproveDeclassify), }) } // FromStackItem populates RolePermissions from a stack item. func (rp *RolePermissions) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 6 { return errPalamInvalidStackItem } viewAgg, err := arr[0].TryBool() if err != nil { return err } rp.CanViewAggregate = viewAgg viewId, err := arr[1].TryBool() if err != nil { return err } rp.CanViewIdentities = viewId viewDoc, err := arr[2].TryBool() if err != nil { return err } rp.CanViewDocuments = viewDoc attach, err := arr[3].TryBool() if err != nil { return err } rp.CanAttachData = attach reqDecl, err := arr[4].TryBool() if err != nil { return err } rp.CanRequestDeclassify = reqDecl appDecl, err := arr[5].TryBool() if err != nil { return err } rp.CanApproveDeclassify = appDecl return nil } // PalamConfig represents configurable parameters for TransparencyLedger. type PalamConfig struct { MinApprovals uint32 // Minimum approvals for declassification (default: 2) MaxApprovals uint32 // Maximum approvers allowed (default: 5) DefaultExpiration uint32 // Default expiration in blocks (default: 7 days worth) LogAllAccess bool // Whether to log all access (default: true) LogRetentionDays uint32 // How long to keep logs (default: 365) MaxFlowsPerBlock uint32 // Rate limit for flows (default: 1000) MaxRequestsPerDay uint32 // Rate limit for declassify requests (default: 10) } // ToStackItem converts PalamConfig to a stack item. func (lc *PalamConfig) ToStackItem() stackitem.Item { return stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(lc.MinApprovals))), stackitem.NewBigInteger(big.NewInt(int64(lc.MaxApprovals))), stackitem.NewBigInteger(big.NewInt(int64(lc.DefaultExpiration))), stackitem.NewBool(lc.LogAllAccess), stackitem.NewBigInteger(big.NewInt(int64(lc.LogRetentionDays))), stackitem.NewBigInteger(big.NewInt(int64(lc.MaxFlowsPerBlock))), stackitem.NewBigInteger(big.NewInt(int64(lc.MaxRequestsPerDay))), }) } // FromStackItem populates PalamConfig from a stack item. func (lc *PalamConfig) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok || len(arr) < 7 { return errPalamInvalidStackItem } minApprovals, err := arr[0].TryInteger() if err != nil { return err } lc.MinApprovals = uint32(minApprovals.Uint64()) maxApprovals, err := arr[1].TryInteger() if err != nil { return err } lc.MaxApprovals = uint32(maxApprovals.Uint64()) defaultExp, err := arr[2].TryInteger() if err != nil { return err } lc.DefaultExpiration = uint32(defaultExp.Uint64()) logAll, err := arr[3].TryBool() if err != nil { return err } lc.LogAllAccess = logAll retention, err := arr[4].TryInteger() if err != nil { return err } lc.LogRetentionDays = uint32(retention.Uint64()) maxFlows, err := arr[5].TryInteger() if err != nil { return err } lc.MaxFlowsPerBlock = uint32(maxFlows.Uint64()) maxReqs, err := arr[6].TryInteger() if err != nil { return err } lc.MaxRequestsPerDay = uint32(maxReqs.Uint64()) return nil } // DefaultPalamConfig returns the default configuration. func DefaultPalamConfig() *PalamConfig { return &PalamConfig{ MinApprovals: 2, MaxApprovals: 5, DefaultExpiration: 604800, // ~7 days in blocks (1 block/sec) LogAllAccess: true, LogRetentionDays: 365, MaxFlowsPerBlock: 1000, MaxRequestsPerDay: 10, } } // DefaultRolePermissions returns permissions for each role. func DefaultRolePermissions(role PalamRole) *RolePermissions { switch role { case PalamRoleConsumer: return &RolePermissions{ CanViewAggregate: true, CanViewIdentities: false, CanViewDocuments: false, CanAttachData: false, CanRequestDeclassify: false, CanApproveDeclassify: false, } case PalamRoleMerchant, PalamRoleDistributor, PalamRoleProducer, PalamRoleNGO: return &RolePermissions{ CanViewAggregate: true, CanViewIdentities: true, CanViewDocuments: true, CanAttachData: true, CanRequestDeclassify: false, CanApproveDeclassify: false, } case PalamRoleAuditor: return &RolePermissions{ CanViewAggregate: true, CanViewIdentities: false, CanViewDocuments: false, CanAttachData: false, CanRequestDeclassify: true, CanApproveDeclassify: false, } default: return &RolePermissions{} } }