tutus-chain/pkg/core/state/palam.go

714 lines
18 KiB
Go

package state
import (
"errors"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.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{}
}
}