tutus-chain/pkg/core/native/ancora.go

1308 lines
44 KiB
Go

package native
import (
"errors"
"fmt"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/config"
"github.com/tutus-one/tutus-chain/pkg/core/dao"
"github.com/tutus-one/tutus-chain/pkg/core/interop"
"github.com/tutus-one/tutus-chain/pkg/core/interop/runtime"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
"github.com/tutus-one/tutus-chain/pkg/core/state"
"github.com/tutus-one/tutus-chain/pkg/core/storage"
"github.com/tutus-one/tutus-chain/pkg/crypto/hash"
"github.com/tutus-one/tutus-chain/pkg/smartcontract"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// Ancora represents the Ancora native contract for anchoring Merkle roots of off-chain data.
// Latin: "ancora" = anchor - anchors off-chain data to on-chain verification.
type Ancora struct {
interop.ContractMD
Vita IVita
Tutus ITutus
Audit *AuditLogger // ARCH-005: Comprehensive audit logging
}
// AncoraCache holds cached configuration for the Ancora contract.
type AncoraCache struct {
config state.StateAnchorsConfig
}
// Storage prefixes for Ancora contract.
const (
ancoraConfigPrefix = 0x01
ancoraRootPrefix = 0x10 // vitaID + dataType -> RootInfo
ancoraHistoryPrefix = 0x11 // vitaID + dataType + version -> RootInfo
ancoraProviderPrefix = 0x20 // dataType + provider -> ProviderConfig
ancoraErasurePrefix = 0x30 // vitaID + dataType -> ErasureInfo
ancoraUpdateCountPrefix = 0x40 // blockHeight + provider -> count
ancoraLastUpdatePrefix = 0x41 // vitaID + dataType + provider -> blockHeight
ancoraAttestationPrefix = 0x50 // attestationHash -> AttestationInfo
)
// Errors for Ancora contract.
var (
ErrAncoraInvalidDataType = errors.New("invalid data type")
ErrAncoraInvalidRoot = errors.New("invalid Merkle root: must be 32 bytes")
ErrAncoraProviderNotFound = errors.New("provider not found")
ErrAncoraProviderInactive = errors.New("provider is inactive")
ErrAncoraProviderExists = errors.New("provider already registered")
ErrAncoraUnauthorized = errors.New("unauthorized: caller is not authorized provider")
ErrAncoraRateLimited = errors.New("rate limit exceeded")
ErrAncoraUpdateCooldown = errors.New("update cooldown not elapsed")
ErrAncoraNoRoot = errors.New("no root found for vitaID and dataType")
ErrAncoraInvalidProof = errors.New("invalid Merkle proof")
ErrAncoraProofTooDeep = errors.New("proof exceeds maximum depth")
ErrAncoraVersionNotFound = errors.New("version not found in history")
ErrAncoraErasurePending = errors.New("erasure already pending")
ErrAncoraErasureNotPending = errors.New("no pending erasure request")
ErrAncoraErasureGracePeriod = errors.New("erasure grace period not elapsed")
ErrAncoraInvalidAttestation = errors.New("invalid attestation")
ErrAncoraAttestationExpired = errors.New("attestation has expired")
ErrAncoraVitaNotFound = errors.New("vita not found")
)
var (
_ interop.Contract = (*Ancora)(nil)
_ dao.NativeContractCache = (*AncoraCache)(nil)
)
// Copy implements NativeContractCache interface.
func (c *AncoraCache) Copy() dao.NativeContractCache {
cp := &AncoraCache{config: c.config}
return cp
}
// newAncora returns a new Ancora native contract.
func newAncora() *Ancora {
a := &Ancora{
ContractMD: *interop.NewContractMD(nativenames.Ancora, nativeids.Ancora, nil),
Audit: NewAuditLogger(nativeids.Ancora),
}
defer a.BuildHFSpecificMD(a.ActiveIn())
// Configuration methods
desc := NewDescriptor("getConfig", smartcontract.ArrayType)
md := NewMethodAndPrice(a.getConfig, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
desc = NewDescriptor("setConfig", smartcontract.VoidType,
manifest.NewParameter("config", smartcontract.ArrayType))
md = NewMethodAndPrice(a.setConfig, 1<<15, callflag.States)
a.AddMethod(md, desc)
// Provider management
desc = NewDescriptor("registerProvider", smartcontract.BoolType,
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("provider", smartcontract.Hash160Type),
manifest.NewParameter("description", smartcontract.StringType),
manifest.NewParameter("maxUpdatesPerBlock", smartcontract.IntegerType),
manifest.NewParameter("updateCooldown", smartcontract.IntegerType))
md = NewMethodAndPrice(a.registerProvider, 1<<15, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("revokeProvider", smartcontract.BoolType,
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("provider", smartcontract.Hash160Type))
md = NewMethodAndPrice(a.revokeProvider, 1<<15, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("getProvider", smartcontract.ArrayType,
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("provider", smartcontract.Hash160Type))
md = NewMethodAndPrice(a.getProvider, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
desc = NewDescriptor("isProviderActive", smartcontract.BoolType,
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("provider", smartcontract.Hash160Type))
md = NewMethodAndPrice(a.isProviderActive, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
// Root management
desc = NewDescriptor("updateDataRoot", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("root", smartcontract.ByteArrayType),
manifest.NewParameter("leafCount", smartcontract.IntegerType),
manifest.NewParameter("treeAlgorithm", smartcontract.IntegerType),
manifest.NewParameter("schemaVersion", smartcontract.StringType),
manifest.NewParameter("contentHash", smartcontract.ByteArrayType))
md = NewMethodAndPrice(a.updateDataRoot, 1<<16, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("getDataRoot", smartcontract.ArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType))
md = NewMethodAndPrice(a.getDataRoot, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
desc = NewDescriptor("getDataRootAtVersion", smartcontract.ArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("version", smartcontract.IntegerType))
md = NewMethodAndPrice(a.getDataRootAtVersion, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
// Proof verification
desc = NewDescriptor("verifyProof", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("leaf", smartcontract.ByteArrayType),
manifest.NewParameter("proof", smartcontract.ArrayType),
manifest.NewParameter("index", smartcontract.IntegerType))
md = NewMethodAndPrice(a.verifyProof, 1<<16, callflag.ReadStates)
a.AddMethod(md, desc)
desc = NewDescriptor("verifyProofAtVersion", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("version", smartcontract.IntegerType),
manifest.NewParameter("leaf", smartcontract.ByteArrayType),
manifest.NewParameter("proof", smartcontract.ArrayType),
manifest.NewParameter("index", smartcontract.IntegerType))
md = NewMethodAndPrice(a.verifyProofAtVersion, 1<<16, callflag.ReadStates)
a.AddMethod(md, desc)
// GDPR erasure
desc = NewDescriptor("requestErasure", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(a.requestErasure, 1<<15, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("confirmErasure", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType))
md = NewMethodAndPrice(a.confirmErasure, 1<<15, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("denyErasure", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(a.denyErasure, 1<<15, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("getErasureInfo", smartcontract.ArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataType", smartcontract.IntegerType))
md = NewMethodAndPrice(a.getErasureInfo, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
// Data portability
desc = NewDescriptor("generatePortabilityAttestation", smartcontract.ByteArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("dataTypes", smartcontract.ArrayType))
md = NewMethodAndPrice(a.generatePortabilityAttestation, 1<<16, callflag.States)
a.AddMethod(md, desc)
desc = NewDescriptor("verifyPortabilityAttestation", smartcontract.BoolType,
manifest.NewParameter("attestation", smartcontract.ByteArrayType))
md = NewMethodAndPrice(a.verifyPortabilityAttestation, 1<<15, callflag.ReadStates)
a.AddMethod(md, desc)
// Audit log query (ARCH-005)
desc = NewDescriptor("getAuditLog", smartcontract.ArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("startBlock", smartcontract.IntegerType),
manifest.NewParameter("endBlock", smartcontract.IntegerType),
manifest.NewParameter("limit", smartcontract.IntegerType))
md = NewMethodAndPrice(a.getAuditLog, 1<<16, callflag.ReadStates)
a.AddMethod(md, desc)
return a
}
// Metadata implements the Contract interface.
func (a *Ancora) Metadata() *interop.ContractMD {
return &a.ContractMD
}
// Initialize initializes Ancora native contract and implements the Contract interface.
func (a *Ancora) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != a.ActiveIn() {
return nil
}
cfg := state.DefaultStateAnchorsConfig()
if err := a.putConfig(ic.DAO, &cfg); err != nil {
return fmt.Errorf("failed to initialize config: %w", err)
}
cache := &AncoraCache{config: cfg}
ic.DAO.SetCache(a.ID, cache)
return nil
}
// InitializeCache implements the Contract interface.
func (a *Ancora) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
cfg, err := a.loadConfig(d)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
cache := &AncoraCache{config: *cfg}
d.SetCache(a.ID, cache)
return nil
}
// OnPersist implements the Contract interface.
func (a *Ancora) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements the Contract interface.
func (a *Ancora) PostPersist(ic *interop.Context) error {
return nil
}
// ActiveIn implements the Contract interface.
func (a *Ancora) ActiveIn() *config.Hardfork {
return nil // Active from genesis
}
// Address returns the contract's script hash.
func (a *Ancora) Address() util.Uint160 {
return a.Hash
}
// ========== Configuration Methods ==========
func (a *Ancora) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
cache := ic.DAO.GetROCache(a.ID).(*AncoraCache)
cfg := cache.config
return stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(cfg.DefaultTreeAlgorithm))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.MaxProofDepth))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.DefaultMaxUpdatesPerBlock))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.DefaultUpdateCooldown))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.MaxHistoryVersions))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.ErasureGracePeriod))),
stackitem.NewBigInteger(big.NewInt(int64(cfg.AttestationValidBlocks))),
})
}
func (a *Ancora) setConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !a.Tutus.CheckCommittee(ic) {
panic("invalid committee signature")
}
arr, ok := args[0].Value().([]stackitem.Item)
if !ok || len(arr) != 7 {
panic("invalid config array")
}
cfg := state.StateAnchorsConfig{
DefaultTreeAlgorithm: state.TreeAlgorithm(toUint32(arr[0])),
MaxProofDepth: toUint32(arr[1]),
DefaultMaxUpdatesPerBlock: toUint32(arr[2]),
DefaultUpdateCooldown: toUint32(arr[3]),
MaxHistoryVersions: toUint32(arr[4]),
ErasureGracePeriod: toUint32(arr[5]),
AttestationValidBlocks: toUint32(arr[6]),
}
if err := a.putConfig(ic.DAO, &cfg); err != nil {
panic(fmt.Errorf("failed to save config: %w", err))
}
cache := ic.DAO.GetRWCache(a.ID).(*AncoraCache)
cache.config = cfg
return stackitem.Null{}
}
func (a *Ancora) putConfig(d *dao.Simple, cfg *state.StateAnchorsConfig) error {
key := []byte{ancoraConfigPrefix}
data := make([]byte, 28) // 7 * 4 bytes
// Simple serialization
putUint32(data[0:4], uint32(cfg.DefaultTreeAlgorithm))
putUint32(data[4:8], cfg.MaxProofDepth)
putUint32(data[8:12], cfg.DefaultMaxUpdatesPerBlock)
putUint32(data[12:16], cfg.DefaultUpdateCooldown)
putUint32(data[16:20], cfg.MaxHistoryVersions)
putUint32(data[20:24], cfg.ErasureGracePeriod)
putUint32(data[24:28], cfg.AttestationValidBlocks)
d.PutStorageItem(a.ID, key, data)
return nil
}
func (a *Ancora) loadConfig(d *dao.Simple) (*state.StateAnchorsConfig, error) {
key := []byte{ancoraConfigPrefix}
data := d.GetStorageItem(a.ID, key)
if data == nil {
return nil, errors.New("config not found")
}
if len(data) != 28 {
return nil, errors.New("invalid config data length")
}
cfg := &state.StateAnchorsConfig{
DefaultTreeAlgorithm: state.TreeAlgorithm(getUint32(data[0:4])),
MaxProofDepth: getUint32(data[4:8]),
DefaultMaxUpdatesPerBlock: getUint32(data[8:12]),
DefaultUpdateCooldown: getUint32(data[12:16]),
MaxHistoryVersions: getUint32(data[16:20]),
ErasureGracePeriod: getUint32(data[20:24]),
AttestationValidBlocks: getUint32(data[24:28]),
}
return cfg, nil
}
// ========== Provider Management ==========
func (a *Ancora) registerProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !a.Tutus.CheckCommittee(ic) {
panic("invalid committee signature")
}
dataType := state.DataType(toUint32(args[0]))
if dataType > state.DataTypeCustom {
panic(ErrAncoraInvalidDataType)
}
// Note: Valid types are 0-5 (Medical, Education, Investment, Documents, Presence, Custom)
provider := toUint160(args[1])
description := toString(args[2])
maxUpdatesPerBlock := toUint32(args[3])
updateCooldown := toUint32(args[4])
// Check if provider already exists
existing := a.getProviderConfig(ic.DAO, dataType, provider)
if existing != nil {
panic(ErrAncoraProviderExists)
}
cfg := &state.ProviderConfig{
DataType: dataType,
Provider: provider,
Description: description,
RegisteredAt: ic.BlockHeight(),
Active: true,
MaxUpdatesPerBlock: maxUpdatesPerBlock,
UpdateCooldown: updateCooldown,
}
if err := a.putProviderConfig(ic.DAO, cfg); err != nil {
panic(fmt.Errorf("failed to register provider: %w", err))
}
// ARCH-005: Audit log provider registration
a.Audit.LogGovernance(ic.DAO, ic.VM.GetCallingScriptHash(), "ancora.provider_registered",
fmt.Sprintf("dataType=%d provider=%s desc=%s", dataType, provider.StringLE(), description),
AuditOutcomeSuccess, ic.BlockHeight())
ic.AddNotification(a.Hash, "ProviderRegistered", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray(provider.BytesBE()),
stackitem.NewByteArray([]byte(description)),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) revokeProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !a.Tutus.CheckCommittee(ic) {
panic("invalid committee signature")
}
dataType := state.DataType(toUint32(args[0]))
provider := toUint160(args[1])
cfg := a.getProviderConfig(ic.DAO, dataType, provider)
if cfg == nil {
panic(ErrAncoraProviderNotFound)
}
cfg.Active = false
if err := a.putProviderConfig(ic.DAO, cfg); err != nil {
panic(fmt.Errorf("failed to revoke provider: %w", err))
}
// ARCH-005: Audit log provider revocation
a.Audit.LogGovernance(ic.DAO, ic.VM.GetCallingScriptHash(), "ancora.provider_revoked",
fmt.Sprintf("dataType=%d provider=%s", dataType, provider.StringLE()),
AuditOutcomeSuccess, ic.BlockHeight())
ic.AddNotification(a.Hash, "ProviderRevoked", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray(provider.BytesBE()),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) getProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item {
dataType := state.DataType(toUint32(args[0]))
provider := toUint160(args[1])
cfg := a.getProviderConfig(ic.DAO, dataType, provider)
if cfg == nil {
return stackitem.Null{}
}
item, err := cfg.ToStackItem()
if err != nil {
panic(fmt.Errorf("failed to convert provider config to stack item: %w", err))
}
return item
}
func (a *Ancora) isProviderActive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
dataType := state.DataType(toUint32(args[0]))
provider := toUint160(args[1])
cfg := a.getProviderConfig(ic.DAO, dataType, provider)
return stackitem.NewBool(cfg != nil && cfg.Active)
}
func (a *Ancora) getProviderConfig(d *dao.Simple, dataType state.DataType, provider util.Uint160) *state.ProviderConfig {
key := append([]byte{ancoraProviderPrefix, byte(dataType)}, provider.BytesBE()...)
cfg := new(state.ProviderConfig)
err := getConvertibleFromDAO(a.ID, d, key, cfg)
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return nil
}
panic(fmt.Errorf("failed to get provider config: %w", err))
}
return cfg
}
func (a *Ancora) putProviderConfig(d *dao.Simple, cfg *state.ProviderConfig) error {
key := append([]byte{ancoraProviderPrefix, byte(cfg.DataType)}, cfg.Provider.BytesBE()...)
return putConvertibleToDAO(a.ID, d, key, cfg)
}
// ========== Root Management ==========
func (a *Ancora) updateDataRoot(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
root, err := args[2].TryBytes()
if err != nil {
panic(ErrAncoraInvalidRoot)
}
if len(root) != 32 {
panic(ErrAncoraInvalidRoot)
}
leafCount := toUint64(args[3])
treeAlgorithm := state.TreeAlgorithm(toUint32(args[4]))
schemaVersion := toString(args[5])
contentHash, _ := args[6].TryBytes()
// Verify Vita exists
if !a.Vita.ExistsInternal(ic.DAO, vitaID) {
panic(ErrAncoraVitaNotFound)
}
// Verify caller is authorized provider or Vita owner
caller := ic.VM.GetCallingScriptHash()
providerCfg := a.getProviderConfig(ic.DAO, dataType, caller)
if providerCfg == nil || !providerCfg.Active {
// Check if caller is Vita owner
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
ok, err := runtime.CheckHashedWitness(ic, owner)
if err != nil || !ok {
panic(ErrAncoraUnauthorized)
}
} else {
// Rate limiting for providers
if err := a.checkRateLimit(ic, providerCfg, vitaID, dataType); err != nil {
panic(err)
}
}
// Get current root to determine version
currentRoot := a.getRootInfo(ic.DAO, vitaID, dataType)
var version uint64 = 1
if currentRoot != nil {
version = currentRoot.Version + 1
// Archive current root to history
if err := a.archiveRoot(ic.DAO, vitaID, dataType, currentRoot); err != nil {
panic(fmt.Errorf("failed to archive previous root: %w", err))
}
}
newRoot := &state.RootInfo{
Root: root,
LeafCount: leafCount,
UpdatedAt: ic.BlockHeight(),
UpdatedBy: caller,
Version: version,
TreeAlgorithm: treeAlgorithm,
SchemaVersion: schemaVersion,
ContentHash: contentHash,
}
if err := a.putRootInfo(ic.DAO, vitaID, dataType, newRoot); err != nil {
panic(fmt.Errorf("failed to update root: %w", err))
}
// Update rate limit tracking
a.recordUpdate(ic.DAO, ic.BlockHeight(), caller, vitaID, dataType)
// ARCH-005: Audit log data root update
// For GDPR compliance: the on-chain audit entry serves as immutable proof
// that an update occurred, even if off-chain data is later deleted
prevStateHash := util.Uint256{}
newStateHash := hash.Sha256(root)
if currentRoot != nil {
prevStateHash = hash.Sha256(currentRoot.Root)
}
resourceID := make([]byte, 9)
putUint64(resourceID[0:8], vitaID)
resourceID[8] = byte(dataType)
a.Audit.LogDataChange(ic.DAO, caller, nativeids.Ancora, "ancora.data_root_updated",
resourceID, prevStateHash, newStateHash, ic.BlockHeight())
ic.AddNotification(a.Hash, "DataRootUpdated", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray(root),
stackitem.NewBigInteger(big.NewInt(int64(version))),
stackitem.NewByteArray(caller.BytesBE()),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) getDataRoot(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
root := a.getRootInfo(ic.DAO, vitaID, dataType)
if root == nil {
return stackitem.Null{}
}
item, err := root.ToStackItem()
if err != nil {
panic(fmt.Errorf("failed to convert root to stack item: %w", err))
}
return item
}
func (a *Ancora) getDataRootAtVersion(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
version := toUint64(args[2])
// First check current version
current := a.getRootInfo(ic.DAO, vitaID, dataType)
if current != nil && current.Version == version {
item, err := current.ToStackItem()
if err != nil {
panic(fmt.Errorf("failed to convert root to stack item: %w", err))
}
return item
}
// Check history
root := a.getHistoricalRoot(ic.DAO, vitaID, dataType, version)
if root == nil {
return stackitem.Null{}
}
item, err := root.ToStackItem()
if err != nil {
panic(fmt.Errorf("failed to convert root to stack item: %w", err))
}
return item
}
func (a *Ancora) getRootInfo(d *dao.Simple, vitaID uint64, dataType state.DataType) *state.RootInfo {
key := a.makeRootKey(vitaID, dataType)
root := new(state.RootInfo)
err := getConvertibleFromDAO(a.ID, d, key, root)
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return nil
}
panic(fmt.Errorf("failed to get root info: %w", err))
}
return root
}
func (a *Ancora) putRootInfo(d *dao.Simple, vitaID uint64, dataType state.DataType, root *state.RootInfo) error {
key := a.makeRootKey(vitaID, dataType)
return putConvertibleToDAO(a.ID, d, key, root)
}
func (a *Ancora) getHistoricalRoot(d *dao.Simple, vitaID uint64, dataType state.DataType, version uint64) *state.RootInfo {
key := a.makeHistoryKey(vitaID, dataType, version)
root := new(state.RootInfo)
err := getConvertibleFromDAO(a.ID, d, key, root)
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return nil
}
panic(fmt.Errorf("failed to get historical root: %w", err))
}
return root
}
func (a *Ancora) archiveRoot(d *dao.Simple, vitaID uint64, dataType state.DataType, root *state.RootInfo) error {
key := a.makeHistoryKey(vitaID, dataType, root.Version)
return putConvertibleToDAO(a.ID, d, key, root)
}
func (a *Ancora) makeRootKey(vitaID uint64, dataType state.DataType) []byte {
key := make([]byte, 1+8+1)
key[0] = ancoraRootPrefix
putUint64(key[1:9], vitaID)
key[9] = byte(dataType)
return key
}
func (a *Ancora) makeHistoryKey(vitaID uint64, dataType state.DataType, version uint64) []byte {
key := make([]byte, 1+8+1+8)
key[0] = ancoraHistoryPrefix
putUint64(key[1:9], vitaID)
key[9] = byte(dataType)
putUint64(key[10:18], version)
return key
}
// ========== Proof Verification ==========
func (a *Ancora) verifyProof(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
leaf, err := args[2].TryBytes()
if err != nil {
panic(ErrAncoraInvalidProof)
}
proofArr, ok := args[3].Value().([]stackitem.Item)
if !ok {
panic(ErrAncoraInvalidProof)
}
proof := make([][]byte, len(proofArr))
for i, item := range proofArr {
proof[i], err = item.TryBytes()
if err != nil {
panic(ErrAncoraInvalidProof)
}
}
index := toUint64(args[4])
rootInfo := a.getRootInfo(ic.DAO, vitaID, dataType)
if rootInfo == nil {
return stackitem.NewBool(false)
}
valid := a.verifyMerkleProofInternal(rootInfo.Root, leaf, proof, index, rootInfo.TreeAlgorithm)
// ARCH-005: Audit log proof verification (access tracking)
caller := ic.VM.GetCallingScriptHash()
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
resourceID := make([]byte, 9)
putUint64(resourceID[0:8], vitaID)
resourceID[8] = byte(dataType)
outcome := AuditOutcomeSuccess
if !valid {
outcome = AuditOutcomeFailure
}
a.Audit.LogAccess(ic.DAO, caller, owner, "ancora.proof_verified", resourceID, outcome, ic.BlockHeight())
return stackitem.NewBool(valid)
}
func (a *Ancora) verifyProofAtVersion(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
version := toUint64(args[2])
leaf, err := args[3].TryBytes()
if err != nil {
panic(ErrAncoraInvalidProof)
}
proofArr, ok := args[4].Value().([]stackitem.Item)
if !ok {
panic(ErrAncoraInvalidProof)
}
proof := make([][]byte, len(proofArr))
for i, item := range proofArr {
proof[i], err = item.TryBytes()
if err != nil {
panic(ErrAncoraInvalidProof)
}
}
index := toUint64(args[5])
// Check current version first
rootInfo := a.getRootInfo(ic.DAO, vitaID, dataType)
if rootInfo != nil && rootInfo.Version == version {
valid := a.verifyMerkleProofInternal(rootInfo.Root, leaf, proof, index, rootInfo.TreeAlgorithm)
a.logProofVerification(ic, vitaID, dataType, version, valid)
return stackitem.NewBool(valid)
}
// Check historical version
rootInfo = a.getHistoricalRoot(ic.DAO, vitaID, dataType, version)
if rootInfo == nil {
return stackitem.NewBool(false)
}
valid := a.verifyMerkleProofInternal(rootInfo.Root, leaf, proof, index, rootInfo.TreeAlgorithm)
a.logProofVerification(ic, vitaID, dataType, version, valid)
return stackitem.NewBool(valid)
}
func (a *Ancora) verifyMerkleProofInternal(root, leaf []byte, proof [][]byte, index uint64, algorithm state.TreeAlgorithm) bool {
if len(root) != 32 {
return false
}
// Check proof depth
cache := a.getCache(nil)
if cache != nil && uint32(len(proof)) > cache.config.MaxProofDepth {
return false
}
// Compute hash based on algorithm
// Note: Currently only SHA256 is implemented. Keccak256 and Poseidon are TODO.
var computed []byte
switch algorithm {
case state.TreeAlgorithmSHA256:
computed = hash.Sha256(leaf).BytesBE()
case state.TreeAlgorithmKeccak256:
// TODO: Implement Keccak256 when needed for EVM compatibility
computed = hash.Sha256(leaf).BytesBE()
default:
// Poseidon not yet implemented, fallback to SHA256
computed = hash.Sha256(leaf).BytesBE()
}
// Traverse the proof
for _, sibling := range proof {
if len(sibling) != 32 {
return false
}
var combined []byte
if index%2 == 0 {
combined = append(computed, sibling...)
} else {
combined = append(sibling, computed...)
}
switch algorithm {
case state.TreeAlgorithmSHA256:
computed = hash.Sha256(combined).BytesBE()
case state.TreeAlgorithmKeccak256:
// TODO: Implement Keccak256 when needed for EVM compatibility
computed = hash.Sha256(combined).BytesBE()
default:
computed = hash.Sha256(combined).BytesBE()
}
index /= 2
}
// Compare computed root with stored root
if len(computed) != len(root) {
return false
}
for i := range computed {
if computed[i] != root[i] {
return false
}
}
return true
}
// VerifyProofInternal is a cross-contract method for other native contracts.
func (a *Ancora) VerifyProofInternal(d *dao.Simple, vitaID uint64, dataType state.DataType, leaf []byte, proof [][]byte, index uint64) bool {
rootInfo := a.getRootInfo(d, vitaID, dataType)
if rootInfo == nil {
return false
}
return a.verifyMerkleProofInternal(rootInfo.Root, leaf, proof, index, rootInfo.TreeAlgorithm)
}
// RequireValidRoot is a cross-contract method that panics if no valid root exists.
func (a *Ancora) RequireValidRoot(d *dao.Simple, vitaID uint64, dataType state.DataType) {
root := a.getRootInfo(d, vitaID, dataType)
if root == nil {
panic(ErrAncoraNoRoot)
}
}
// logProofVerification logs a proof verification to the audit trail.
func (a *Ancora) logProofVerification(ic *interop.Context, vitaID uint64, dataType state.DataType, version uint64, valid bool) {
caller := ic.VM.GetCallingScriptHash()
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
resourceID := make([]byte, 17)
putUint64(resourceID[0:8], vitaID)
resourceID[8] = byte(dataType)
putUint64(resourceID[9:17], version)
outcome := AuditOutcomeSuccess
if !valid {
outcome = AuditOutcomeFailure
}
a.Audit.LogAccess(ic.DAO, caller, owner, "ancora.proof_verified_at_version", resourceID, outcome, ic.BlockHeight())
}
// ========== GDPR Erasure ==========
func (a *Ancora) requestErasure(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
reason := toString(args[2])
// Verify caller is Vita owner
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
ok, err := runtime.CheckHashedWitness(ic, owner)
if err != nil || !ok {
panic(ErrAncoraUnauthorized)
}
// Check for existing erasure request
existing := a.getErasureInfoInternal(ic.DAO, vitaID, dataType)
if existing != nil && existing.Status == state.ErasurePending {
panic(ErrAncoraErasurePending)
}
erasure := &state.ErasureInfo{
RequestedAt: ic.BlockHeight(),
RequestedBy: owner,
Reason: reason,
Status: state.ErasurePending,
}
if err := a.putErasureInfo(ic.DAO, vitaID, dataType, erasure); err != nil {
panic(fmt.Errorf("failed to save erasure request: %w", err))
}
// ARCH-005: Audit log erasure request (GDPR Article 17 - Right to be forgotten)
// This audit entry is CRITICAL for compliance - proves user exercised their right
resourceID := make([]byte, 9)
putUint64(resourceID[0:8], vitaID)
resourceID[8] = byte(dataType)
a.Audit.LogCompliance(ic.DAO, owner, util.Uint160{}, "ancora.erasure_requested",
fmt.Sprintf("vitaID=%d dataType=%d reason=%s", vitaID, dataType, reason), ic.BlockHeight())
ic.AddNotification(a.Hash, "ErasureRequested", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray([]byte(reason)),
stackitem.NewBigInteger(big.NewInt(int64(ic.BlockHeight()))),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) confirmErasure(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
// Verify caller is authorized provider
caller := ic.VM.GetCallingScriptHash()
providerCfg := a.getProviderConfig(ic.DAO, dataType, caller)
if providerCfg == nil || !providerCfg.Active {
panic(ErrAncoraUnauthorized)
}
erasure := a.getErasureInfoInternal(ic.DAO, vitaID, dataType)
if erasure == nil || erasure.Status != state.ErasurePending {
panic(ErrAncoraErasureNotPending)
}
erasure.Status = state.ErasureConfirmed
erasure.ProcessedAt = ic.BlockHeight()
erasure.ConfirmedBy = caller
if err := a.putErasureInfo(ic.DAO, vitaID, dataType, erasure); err != nil {
panic(fmt.Errorf("failed to confirm erasure: %w", err))
}
// Clear the data root
rootKey := a.makeRootKey(vitaID, dataType)
ic.DAO.DeleteStorageItem(a.ID, rootKey)
// ARCH-005: Audit log erasure confirmation (GDPR Article 17 fulfilled)
// This audit entry proves we complied with the erasure request
// Note: The on-chain hash of this audit entry remains as proof of compliance
a.Audit.LogCompliance(ic.DAO, caller, erasure.RequestedBy, "ancora.erasure_confirmed",
fmt.Sprintf("vitaID=%d dataType=%d requestedAt=%d", vitaID, dataType, erasure.RequestedAt),
ic.BlockHeight())
ic.AddNotification(a.Hash, "ErasureConfirmed", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray(caller.BytesBE()),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) denyErasure(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
reason := toString(args[2])
// Check grace period
erasure := a.getErasureInfoInternal(ic.DAO, vitaID, dataType)
if erasure == nil || erasure.Status != state.ErasurePending {
panic(ErrAncoraErasureNotPending)
}
cache := ic.DAO.GetROCache(a.ID).(*AncoraCache)
if ic.BlockHeight() < erasure.RequestedAt+cache.config.ErasureGracePeriod {
panic(ErrAncoraErasureGracePeriod)
}
// Verify caller is authorized provider or committee
caller := ic.VM.GetCallingScriptHash()
providerCfg := a.getProviderConfig(ic.DAO, dataType, caller)
if (providerCfg == nil || !providerCfg.Active) && !a.Tutus.CheckCommittee(ic) {
panic(ErrAncoraUnauthorized)
}
erasure.Status = state.ErasureDenied
erasure.ProcessedAt = ic.BlockHeight()
erasure.ConfirmedBy = caller
erasure.DeniedReason = reason
if err := a.putErasureInfo(ic.DAO, vitaID, dataType, erasure); err != nil {
panic(fmt.Errorf("failed to deny erasure: %w", err))
}
// ARCH-005: Audit log erasure denial (GDPR Article 17 exception invoked)
// Critical for legal compliance - documents the legal basis for denial
a.Audit.LogCompliance(ic.DAO, caller, erasure.RequestedBy, "ancora.erasure_denied",
fmt.Sprintf("vitaID=%d dataType=%d reason=%s", vitaID, dataType, reason),
ic.BlockHeight())
ic.AddNotification(a.Hash, "ErasureDenied", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewBigInteger(big.NewInt(int64(dataType))),
stackitem.NewByteArray([]byte(reason)),
}))
return stackitem.NewBool(true)
}
func (a *Ancora) getErasureInfo(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataType := state.DataType(toUint32(args[1]))
erasure := a.getErasureInfoFromDAO(ic.DAO, vitaID, dataType)
if erasure == nil {
return stackitem.Null{}
}
item, err := erasure.ToStackItem()
if err != nil {
panic(fmt.Errorf("failed to convert erasure to stack item: %w", err))
}
return item
}
func (a *Ancora) getErasureInfoFromDAO(d *dao.Simple, vitaID uint64, dataType state.DataType) *state.ErasureInfo {
return a.getErasureInfoInternal(d, vitaID, dataType)
}
func (a *Ancora) getErasureInfoInternal(d *dao.Simple, vitaID uint64, dataType state.DataType) *state.ErasureInfo {
key := a.makeErasureKey(vitaID, dataType)
erasure := new(state.ErasureInfo)
err := getConvertibleFromDAO(a.ID, d, key, erasure)
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return nil
}
panic(fmt.Errorf("failed to get erasure info: %w", err))
}
return erasure
}
func (a *Ancora) putErasureInfo(d *dao.Simple, vitaID uint64, dataType state.DataType, erasure *state.ErasureInfo) error {
key := a.makeErasureKey(vitaID, dataType)
return putConvertibleToDAO(a.ID, d, key, erasure)
}
func (a *Ancora) makeErasureKey(vitaID uint64, dataType state.DataType) []byte {
key := make([]byte, 1+8+1)
key[0] = ancoraErasurePrefix
putUint64(key[1:9], vitaID)
key[9] = byte(dataType)
return key
}
// ========== Data Portability ==========
func (a *Ancora) generatePortabilityAttestation(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
dataTypesArr, ok := args[1].Value().([]stackitem.Item)
if !ok {
panic("invalid dataTypes array")
}
// Verify caller is Vita owner
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
ok2, err := runtime.CheckHashedWitness(ic, owner)
if err != nil || !ok2 {
panic(ErrAncoraUnauthorized)
}
// Build attestation data
attestData := make([]byte, 0, 8+4+32*len(dataTypesArr))
attestData = appendUint64(attestData, vitaID)
attestData = appendUint32(attestData, ic.BlockHeight())
for _, item := range dataTypesArr {
dataType := state.DataType(toUint32(item))
rootInfo := a.getRootInfo(ic.DAO, vitaID, dataType)
if rootInfo != nil {
attestData = append(attestData, byte(dataType))
attestData = append(attestData, rootInfo.Root...)
}
}
// Hash the attestation
attestHash := hash.Sha256(attestData).BytesBE()
// Store attestation with expiry
cache := ic.DAO.GetROCache(a.ID).(*AncoraCache)
expiryBlock := ic.BlockHeight() + cache.config.AttestationValidBlocks
key := append([]byte{ancoraAttestationPrefix}, attestHash...)
value := make([]byte, 4)
putUint32(value, expiryBlock)
ic.DAO.PutStorageItem(a.ID, key, value)
ic.AddNotification(a.Hash, "PortabilityAttestationGenerated", stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewByteArray(attestHash),
stackitem.NewBigInteger(big.NewInt(int64(expiryBlock))),
}))
return stackitem.NewByteArray(attestData)
}
func (a *Ancora) verifyPortabilityAttestation(ic *interop.Context, args []stackitem.Item) stackitem.Item {
attestation, err := args[0].TryBytes()
if err != nil {
panic(ErrAncoraInvalidAttestation)
}
attestHash := hash.Sha256(attestation).BytesBE()
key := append([]byte{ancoraAttestationPrefix}, attestHash...)
value := ic.DAO.GetStorageItem(a.ID, key)
if value == nil {
return stackitem.NewBool(false)
}
expiryBlock := getUint32(value)
if ic.BlockHeight() > expiryBlock {
return stackitem.NewBool(false)
}
return stackitem.NewBool(true)
}
// ========== Audit Log Query ==========
// getAuditLog retrieves audit log entries for a specific Vita or globally.
// This provides GDPR-compliant transparency: citizens can see who accessed their data.
func (a *Ancora) getAuditLog(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
startBlock := toUint32(args[1])
endBlock := toUint32(args[2])
limit := int(toUint32(args[3]))
if limit <= 0 || limit > 100 {
limit = 100 // Cap at 100 entries per query
}
// If vitaID is specified, verify caller is the owner or committee
if vitaID > 0 {
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
caller := ic.VM.GetCallingScriptHash()
// Check if caller is the Vita owner
ok, err := runtime.CheckHashedWitness(ic, owner)
if err != nil || !ok {
// Not the owner - check if committee
if !a.Tutus.CheckCommittee(ic) {
// Check if caller is an authorized provider for this data
isProvider := false
for dt := state.DataType(0); dt <= state.DataTypeCustom; dt++ {
if cfg := a.getProviderConfig(ic.DAO, dt, caller); cfg != nil && cfg.Active {
isProvider = true
break
}
}
if !isProvider {
panic(ErrAncoraUnauthorized)
}
}
}
} else {
// Global audit query - requires committee
if !a.Tutus.CheckCommittee(ic) {
panic(ErrAncoraUnauthorized)
}
}
// Build query
query := &AuditQuery{
StartBlock: startBlock,
EndBlock: endBlock,
Limit: limit,
}
// If vitaID specified, query by target (owner of Vita)
if vitaID > 0 {
owner := a.Vita.OwnerOfInternal(ic.DAO, vitaID)
query.Target = &owner
}
// Query the audit log
entries := a.Audit.Query(ic.DAO, query)
// Convert entries to stack items
items := make([]stackitem.Item, 0, len(entries))
for _, entry := range entries {
items = append(items, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(entry.EntryID))),
stackitem.NewBigInteger(big.NewInt(int64(entry.Timestamp))),
stackitem.NewBigInteger(big.NewInt(int64(entry.Category))),
stackitem.NewBigInteger(big.NewInt(int64(entry.Severity))),
stackitem.NewBigInteger(big.NewInt(int64(entry.Outcome))),
stackitem.NewByteArray(entry.Actor.BytesBE()),
stackitem.NewByteArray(entry.Target.BytesBE()),
stackitem.NewByteArray([]byte(entry.Action)),
stackitem.NewByteArray(entry.ResourceID),
stackitem.NewByteArray([]byte(entry.Details)),
}))
}
return stackitem.NewArray(items)
}
// ========== Rate Limiting Helpers ==========
func (a *Ancora) checkRateLimit(ic *interop.Context, cfg *state.ProviderConfig, vitaID uint64, dataType state.DataType) error {
// Check per-block rate limit
blockHeight := ic.BlockHeight()
updateCount := a.getUpdateCount(ic.DAO, blockHeight, cfg.Provider)
if updateCount >= cfg.MaxUpdatesPerBlock {
return ErrAncoraRateLimited
}
// Check per-vitaID cooldown
lastUpdate := a.getLastUpdate(ic.DAO, vitaID, dataType, cfg.Provider)
if lastUpdate > 0 && blockHeight < lastUpdate+cfg.UpdateCooldown {
return ErrAncoraUpdateCooldown
}
return nil
}
func (a *Ancora) recordUpdate(d *dao.Simple, blockHeight uint32, provider util.Uint160, vitaID uint64, dataType state.DataType) {
// Increment block update count
countKey := append([]byte{ancoraUpdateCountPrefix}, provider.BytesBE()...)
countKey = appendUint32(countKey, blockHeight)
var count uint32
data := d.GetStorageItem(a.ID, countKey)
if data != nil && len(data) >= 4 {
count = getUint32(data)
}
count++
countData := make([]byte, 4)
putUint32(countData, count)
d.PutStorageItem(a.ID, countKey, countData)
// Record last update for vitaID+dataType+provider
lastKey := a.makeLastUpdateKey(vitaID, dataType, provider)
lastData := make([]byte, 4)
putUint32(lastData, blockHeight)
d.PutStorageItem(a.ID, lastKey, lastData)
}
func (a *Ancora) getUpdateCount(d *dao.Simple, blockHeight uint32, provider util.Uint160) uint32 {
countKey := append([]byte{ancoraUpdateCountPrefix}, provider.BytesBE()...)
countKey = appendUint32(countKey, blockHeight)
data := d.GetStorageItem(a.ID, countKey)
if data == nil || len(data) < 4 {
return 0
}
return getUint32(data)
}
func (a *Ancora) getLastUpdate(d *dao.Simple, vitaID uint64, dataType state.DataType, provider util.Uint160) uint32 {
key := a.makeLastUpdateKey(vitaID, dataType, provider)
data := d.GetStorageItem(a.ID, key)
if data == nil || len(data) < 4 {
return 0
}
return getUint32(data)
}
func (a *Ancora) makeLastUpdateKey(vitaID uint64, dataType state.DataType, provider util.Uint160) []byte {
key := make([]byte, 1+8+1+20)
key[0] = ancoraLastUpdatePrefix
putUint64(key[1:9], vitaID)
key[9] = byte(dataType)
copy(key[10:30], provider.BytesBE())
return key
}
// ========== Utility Helpers ==========
func (a *Ancora) getCache(d *dao.Simple) *AncoraCache {
if d == nil {
return nil
}
cache := d.GetROCache(a.ID)
if cache == nil {
return nil
}
return cache.(*AncoraCache)
}
func putUint32(b []byte, v uint32) {
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
}
func getUint32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func putUint64(b []byte, v uint64) {
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
b[4] = byte(v >> 32)
b[5] = byte(v >> 40)
b[6] = byte(v >> 48)
b[7] = byte(v >> 56)
}
func appendUint32(b []byte, v uint32) []byte {
return append(b, byte(v), byte(v>>8), byte(v>>16), byte(v>>24))
}
func appendUint64(b []byte, v uint64) []byte {
return append(b, byte(v), byte(v>>8), byte(v>>16), byte(v>>24),
byte(v>>32), byte(v>>40), byte(v>>48), byte(v>>56))
}