1261 lines
44 KiB
Go
1261 lines
44 KiB
Go
package native
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"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/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/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"
|
|
)
|
|
|
|
// Pons ("bridge" in Latin) represents the Inter-Government Bridge Protocol
|
|
// native contract. It manages:
|
|
// - Bilateral agreements between sovereign chains
|
|
// - Cross-border verification requests
|
|
// - International VTS settlement
|
|
// - Education and healthcare credential portability
|
|
type Pons struct {
|
|
interop.ContractMD
|
|
NEO INEO
|
|
Vita IVita
|
|
Federation *Federation
|
|
RoleRegistry *RoleRegistry
|
|
VTS *VTS
|
|
Scire *Scire
|
|
Salus *Salus
|
|
}
|
|
|
|
// Storage key prefixes for Pons.
|
|
const (
|
|
ponsPrefixConfig byte = 0x01 // -> PonsConfig
|
|
ponsPrefixAgreement byte = 0x10 // agreementID -> BilateralAgreement
|
|
ponsPrefixAgreementByChain byte = 0x11 // chainID + agreementID -> exists
|
|
ponsPrefixAgreementCounter byte = 0x1F // -> next agreementID
|
|
ponsPrefixVerification byte = 0x20 // requestID -> VerificationRequest
|
|
ponsPrefixVerifBySubject byte = 0x21 // subject + requestID -> exists
|
|
ponsPrefixVerifCounter byte = 0x2F // -> next verificationID
|
|
ponsPrefixSettlement byte = 0x30 // settlementID -> SettlementRequest
|
|
ponsPrefixSettlByChain byte = 0x31 // chainID + settlementID -> exists
|
|
ponsPrefixSettlCounter byte = 0x3F // -> next settlementID
|
|
ponsPrefixCredential byte = 0x40 // credentialID -> CredentialShare
|
|
ponsPrefixCredByOwner byte = 0x41 // owner + credentialID -> exists
|
|
ponsPrefixCredCounter byte = 0x4F // -> next credentialID
|
|
)
|
|
|
|
// Default configuration values.
|
|
const (
|
|
defaultLocalChainID uint32 = 1
|
|
defaultVerificationTimeout uint32 = 8640 // ~1 day at 10s blocks
|
|
defaultSettlementTimeout uint32 = 86400 // ~10 days
|
|
defaultMaxPendingRequests uint64 = 10000
|
|
defaultCredentialShareExpiry uint32 = 315360 // ~1 year
|
|
)
|
|
|
|
// Event names for Pons.
|
|
const (
|
|
AgreementCreatedEvent = "AgreementCreated"
|
|
AgreementUpdatedEvent = "AgreementUpdated"
|
|
AgreementTerminatedEvent = "AgreementTerminated"
|
|
VerificationRequestedEvent = "VerificationRequested"
|
|
VerificationRespondedEvent = "VerificationResponded"
|
|
SettlementRequestedEvent = "SettlementRequested"
|
|
SettlementCompletedEvent = "SettlementCompleted"
|
|
CredentialSharedEvent = "CredentialShared"
|
|
CredentialRevokedEvent = "CredentialRevoked"
|
|
)
|
|
|
|
// Various errors for Pons.
|
|
var (
|
|
ErrNoAgreement = errors.New("no active agreement with target chain")
|
|
ErrAgreementExists = errors.New("agreement already exists")
|
|
ErrAgreementNotFound = errors.New("agreement not found")
|
|
ErrInvalidAgreementType = errors.New("invalid agreement type")
|
|
ErrAgreementNotActive = errors.New("agreement not active")
|
|
ErrVerificationNotFound = errors.New("verification request not found")
|
|
ErrVerificationExpired = errors.New("verification request expired")
|
|
ErrSettlementNotFound = errors.New("settlement request not found")
|
|
ErrSettlementExpired = errors.New("settlement request expired")
|
|
ErrCredentialNotFound = errors.New("credential share not found")
|
|
ErrCredentialExpired = errors.New("credential share expired")
|
|
ErrCredentialRevoked = errors.New("credential share revoked")
|
|
ErrMaxRequestsReached = errors.New("maximum pending requests reached")
|
|
ErrNotCredentialOwner = errors.New("not credential owner")
|
|
ErrAgreementTypeNotAllowed = errors.New("agreement type not allowed for this operation")
|
|
)
|
|
|
|
var _ interop.Contract = (*Pons)(nil)
|
|
|
|
// newPons creates a new Pons native contract.
|
|
func newPons() *Pons {
|
|
p := &Pons{
|
|
ContractMD: *interop.NewContractMD(nativenames.Pons, nativeids.Pons),
|
|
}
|
|
defer p.BuildHFSpecificMD(p.ActiveIn())
|
|
|
|
// getConfig method
|
|
desc := NewDescriptor("getConfig", smartcontract.ArrayType)
|
|
md := NewMethodAndPrice(p.getConfig, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// setLocalChainID method (committee only)
|
|
desc = NewDescriptor("setLocalChainID", smartcontract.BoolType,
|
|
manifest.NewParameter("chainID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setLocalChainID, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Agreement Management ---
|
|
|
|
// createAgreement method (committee only)
|
|
desc = NewDescriptor("createAgreement", smartcontract.IntegerType,
|
|
manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("agreementType", smartcontract.IntegerType),
|
|
manifest.NewParameter("termsHash", smartcontract.Hash256Type),
|
|
manifest.NewParameter("expirationHeight", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.createAgreement, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// updateAgreementStatus method (committee only)
|
|
desc = NewDescriptor("updateAgreementStatus", smartcontract.BoolType,
|
|
manifest.NewParameter("agreementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("newStatus", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.updateAgreementStatus, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getAgreement method
|
|
desc = NewDescriptor("getAgreement", smartcontract.ArrayType,
|
|
manifest.NewParameter("agreementID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getAgreement, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// hasActiveAgreement method
|
|
desc = NewDescriptor("hasActiveAgreement", smartcontract.BoolType,
|
|
manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("agreementType", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.hasActiveAgreement, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Verification Requests ---
|
|
|
|
// requestVerification method
|
|
desc = NewDescriptor("requestVerification", smartcontract.IntegerType,
|
|
manifest.NewParameter("targetChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("subject", smartcontract.Hash160Type),
|
|
manifest.NewParameter("verificationType", smartcontract.IntegerType),
|
|
manifest.NewParameter("dataHash", smartcontract.Hash256Type))
|
|
md = NewMethodAndPrice(p.requestVerification, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// respondVerification method (committee only - represents response from other chain)
|
|
desc = NewDescriptor("respondVerification", smartcontract.BoolType,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType),
|
|
manifest.NewParameter("approved", smartcontract.BoolType),
|
|
manifest.NewParameter("responseHash", smartcontract.Hash256Type))
|
|
md = NewMethodAndPrice(p.respondVerification, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getVerificationRequest method
|
|
desc = NewDescriptor("getVerificationRequest", smartcontract.ArrayType,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getVerificationRequest, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Settlement Requests ---
|
|
|
|
// requestSettlement method
|
|
desc = NewDescriptor("requestSettlement", smartcontract.IntegerType,
|
|
manifest.NewParameter("toChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("receiver", smartcontract.Hash160Type),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
manifest.NewParameter("reference", smartcontract.StringType))
|
|
md = NewMethodAndPrice(p.requestSettlement, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// completeSettlement method (committee only - represents confirmation from other chain)
|
|
desc = NewDescriptor("completeSettlement", smartcontract.BoolType,
|
|
manifest.NewParameter("settlementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("txHash", smartcontract.Hash256Type))
|
|
md = NewMethodAndPrice(p.completeSettlement, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// cancelSettlement method (sender or committee)
|
|
desc = NewDescriptor("cancelSettlement", smartcontract.BoolType,
|
|
manifest.NewParameter("settlementID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.cancelSettlement, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getSettlementRequest method
|
|
desc = NewDescriptor("getSettlementRequest", smartcontract.ArrayType,
|
|
manifest.NewParameter("settlementID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getSettlementRequest, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Credential Sharing ---
|
|
|
|
// shareCredential method
|
|
desc = NewDescriptor("shareCredential", smartcontract.IntegerType,
|
|
manifest.NewParameter("targetChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("credentialType", smartcontract.IntegerType),
|
|
manifest.NewParameter("credentialID", smartcontract.IntegerType),
|
|
manifest.NewParameter("contentHash", smartcontract.Hash256Type),
|
|
manifest.NewParameter("validUntil", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.shareCredential, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// revokeCredentialShare method
|
|
desc = NewDescriptor("revokeCredentialShare", smartcontract.BoolType,
|
|
manifest.NewParameter("shareID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.revokeCredentialShare, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getCredentialShare method
|
|
desc = NewDescriptor("getCredentialShare", smartcontract.ArrayType,
|
|
manifest.NewParameter("shareID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getCredentialShare, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// verifyCredentialShare method
|
|
desc = NewDescriptor("verifyCredentialShare", smartcontract.BoolType,
|
|
manifest.NewParameter("shareID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.verifyCredentialShare, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Counter Query Methods ---
|
|
|
|
// getAgreementCount method
|
|
desc = NewDescriptor("getAgreementCount", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getAgreementCount, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getVerificationCount method
|
|
desc = NewDescriptor("getVerificationCount", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getVerificationCount, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getSettlementCount method
|
|
desc = NewDescriptor("getSettlementCount", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getSettlementCount, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// getCredentialShareCount method
|
|
desc = NewDescriptor("getCredentialShareCount", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getCredentialShareCount, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// --- Events ---
|
|
|
|
eDesc := NewEventDescriptor(AgreementCreatedEvent,
|
|
manifest.NewParameter("agreementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("agreementType", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(AgreementUpdatedEvent,
|
|
manifest.NewParameter("agreementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("oldStatus", smartcontract.IntegerType),
|
|
manifest.NewParameter("newStatus", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(AgreementTerminatedEvent,
|
|
manifest.NewParameter("agreementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("remoteChainID", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(VerificationRequestedEvent,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType),
|
|
manifest.NewParameter("targetChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("subject", smartcontract.Hash160Type),
|
|
manifest.NewParameter("verificationType", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(VerificationRespondedEvent,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType),
|
|
manifest.NewParameter("approved", smartcontract.BoolType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(SettlementRequestedEvent,
|
|
manifest.NewParameter("settlementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("toChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("receiver", smartcontract.Hash160Type),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(SettlementCompletedEvent,
|
|
manifest.NewParameter("settlementID", smartcontract.IntegerType),
|
|
manifest.NewParameter("txHash", smartcontract.Hash256Type))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(CredentialSharedEvent,
|
|
manifest.NewParameter("shareID", smartcontract.IntegerType),
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
|
manifest.NewParameter("targetChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("credentialType", smartcontract.IntegerType))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(CredentialRevokedEvent,
|
|
manifest.NewParameter("shareID", smartcontract.IntegerType),
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
p.AddEvent(NewEvent(eDesc))
|
|
|
|
return p
|
|
}
|
|
|
|
// Metadata returns contract metadata.
|
|
func (p *Pons) Metadata() *interop.ContractMD {
|
|
return &p.ContractMD
|
|
}
|
|
|
|
// Initialize initializes Pons contract at the specified hardfork.
|
|
func (p *Pons) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
|
|
if hf != p.ActiveIn() {
|
|
return nil
|
|
}
|
|
|
|
// Initialize default config
|
|
cfg := state.PonsConfig{
|
|
LocalChainID: defaultLocalChainID,
|
|
VerificationTimeout: defaultVerificationTimeout,
|
|
SettlementTimeout: defaultSettlementTimeout,
|
|
MaxPendingRequests: defaultMaxPendingRequests,
|
|
CredentialShareExpiry: defaultCredentialShareExpiry,
|
|
}
|
|
p.setConfigInternal(ic.DAO, &cfg)
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitializeCache fills native Pons cache from DAO on node restart.
|
|
func (p *Pons) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
|
|
return nil
|
|
}
|
|
|
|
// OnPersist implements the Contract interface.
|
|
func (p *Pons) OnPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// PostPersist implements the Contract interface.
|
|
func (p *Pons) PostPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// ActiveIn returns the hardfork this contract activates in (nil = always active).
|
|
func (p *Pons) ActiveIn() *config.Hardfork {
|
|
return nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Storage Key Helpers
|
|
// ============================================================================
|
|
|
|
func makePonsConfigKey() []byte {
|
|
return []byte{ponsPrefixConfig}
|
|
}
|
|
|
|
func makePonsAgreementKey(agreementID uint64) []byte {
|
|
key := make([]byte, 9)
|
|
key[0] = ponsPrefixAgreement
|
|
binary.BigEndian.PutUint64(key[1:], agreementID)
|
|
return key
|
|
}
|
|
|
|
func makePonsAgreementByChainKey(chainID uint32, agreementID uint64) []byte {
|
|
key := make([]byte, 13)
|
|
key[0] = ponsPrefixAgreementByChain
|
|
binary.BigEndian.PutUint32(key[1:], chainID)
|
|
binary.BigEndian.PutUint64(key[5:], agreementID)
|
|
return key
|
|
}
|
|
|
|
func makePonsAgreementCounterKey() []byte {
|
|
return []byte{ponsPrefixAgreementCounter}
|
|
}
|
|
|
|
func makePonsVerificationKey(requestID uint64) []byte {
|
|
key := make([]byte, 9)
|
|
key[0] = ponsPrefixVerification
|
|
binary.BigEndian.PutUint64(key[1:], requestID)
|
|
return key
|
|
}
|
|
|
|
func makePonsVerifBySubjectKey(subject util.Uint160, requestID uint64) []byte {
|
|
key := make([]byte, 1+util.Uint160Size+8)
|
|
key[0] = ponsPrefixVerifBySubject
|
|
copy(key[1:], subject.BytesBE())
|
|
binary.BigEndian.PutUint64(key[1+util.Uint160Size:], requestID)
|
|
return key
|
|
}
|
|
|
|
func makePonsVerificationCounterKey() []byte {
|
|
return []byte{ponsPrefixVerifCounter}
|
|
}
|
|
|
|
func makePonsSettlementKey(settlementID uint64) []byte {
|
|
key := make([]byte, 9)
|
|
key[0] = ponsPrefixSettlement
|
|
binary.BigEndian.PutUint64(key[1:], settlementID)
|
|
return key
|
|
}
|
|
|
|
func makePonsSettlByChainKey(chainID uint32, settlementID uint64) []byte {
|
|
key := make([]byte, 13)
|
|
key[0] = ponsPrefixSettlByChain
|
|
binary.BigEndian.PutUint32(key[1:], chainID)
|
|
binary.BigEndian.PutUint64(key[5:], settlementID)
|
|
return key
|
|
}
|
|
|
|
func makePonsSettlementCounterKey() []byte {
|
|
return []byte{ponsPrefixSettlCounter}
|
|
}
|
|
|
|
func makePonsCredentialKey(credentialID uint64) []byte {
|
|
key := make([]byte, 9)
|
|
key[0] = ponsPrefixCredential
|
|
binary.BigEndian.PutUint64(key[1:], credentialID)
|
|
return key
|
|
}
|
|
|
|
func makePonsCredByOwnerKey(owner util.Uint160, credentialID uint64) []byte {
|
|
key := make([]byte, 1+util.Uint160Size+8)
|
|
key[0] = ponsPrefixCredByOwner
|
|
copy(key[1:], owner.BytesBE())
|
|
binary.BigEndian.PutUint64(key[1+util.Uint160Size:], credentialID)
|
|
return key
|
|
}
|
|
|
|
func makePonsCredentialCounterKey() []byte {
|
|
return []byte{ponsPrefixCredCounter}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Internal Storage Methods
|
|
// ============================================================================
|
|
|
|
func (p *Pons) getConfigInternal(d *dao.Simple) *state.PonsConfig {
|
|
si := d.GetStorageItem(p.ID, makePonsConfigKey())
|
|
if si == nil {
|
|
return &state.PonsConfig{
|
|
LocalChainID: defaultLocalChainID,
|
|
VerificationTimeout: defaultVerificationTimeout,
|
|
SettlementTimeout: defaultSettlementTimeout,
|
|
MaxPendingRequests: defaultMaxPendingRequests,
|
|
CredentialShareExpiry: defaultCredentialShareExpiry,
|
|
}
|
|
}
|
|
// Decode config: chainID(4) + verifTimeout(4) + settlTimeout(4) + maxReq(8) + credExpiry(4) = 24 bytes
|
|
if len(si) < 24 {
|
|
return &state.PonsConfig{
|
|
LocalChainID: defaultLocalChainID,
|
|
VerificationTimeout: defaultVerificationTimeout,
|
|
SettlementTimeout: defaultSettlementTimeout,
|
|
MaxPendingRequests: defaultMaxPendingRequests,
|
|
CredentialShareExpiry: defaultCredentialShareExpiry,
|
|
}
|
|
}
|
|
return &state.PonsConfig{
|
|
LocalChainID: binary.BigEndian.Uint32(si[0:4]),
|
|
VerificationTimeout: binary.BigEndian.Uint32(si[4:8]),
|
|
SettlementTimeout: binary.BigEndian.Uint32(si[8:12]),
|
|
MaxPendingRequests: binary.BigEndian.Uint64(si[12:20]),
|
|
CredentialShareExpiry: binary.BigEndian.Uint32(si[20:24]),
|
|
}
|
|
}
|
|
|
|
func (p *Pons) setConfigInternal(d *dao.Simple, cfg *state.PonsConfig) {
|
|
buf := make([]byte, 24)
|
|
binary.BigEndian.PutUint32(buf[0:4], cfg.LocalChainID)
|
|
binary.BigEndian.PutUint32(buf[4:8], cfg.VerificationTimeout)
|
|
binary.BigEndian.PutUint32(buf[8:12], cfg.SettlementTimeout)
|
|
binary.BigEndian.PutUint64(buf[12:20], cfg.MaxPendingRequests)
|
|
binary.BigEndian.PutUint32(buf[20:24], cfg.CredentialShareExpiry)
|
|
d.PutStorageItem(p.ID, makePonsConfigKey(), buf)
|
|
}
|
|
|
|
func (p *Pons) getCounterInternal(d *dao.Simple, key []byte) uint64 {
|
|
si := d.GetStorageItem(p.ID, key)
|
|
if si == nil || len(si) < 8 {
|
|
return 0
|
|
}
|
|
return binary.BigEndian.Uint64(si)
|
|
}
|
|
|
|
func (p *Pons) incrementCounterInternal(d *dao.Simple, key []byte) uint64 {
|
|
current := p.getCounterInternal(d, key)
|
|
next := current + 1
|
|
buf := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(buf, next)
|
|
d.PutStorageItem(p.ID, key, buf)
|
|
return next
|
|
}
|
|
|
|
// Agreement storage format:
|
|
// localChainID(4) + remoteChainID(4) + agreementType(1) + status(1) + terms(32) +
|
|
// effectiveDate(4) + expirationDate(4) + createdAt(4) + updatedAt(4) = 58 bytes
|
|
func (p *Pons) getAgreementInternal(d *dao.Simple, agreementID uint64) (*state.BilateralAgreement, bool) {
|
|
si := d.GetStorageItem(p.ID, makePonsAgreementKey(agreementID))
|
|
if si == nil || len(si) < 58 {
|
|
return nil, false
|
|
}
|
|
|
|
var terms util.Uint256
|
|
copy(terms[:], si[10:42])
|
|
|
|
return &state.BilateralAgreement{
|
|
ID: agreementID,
|
|
LocalChainID: binary.BigEndian.Uint32(si[0:4]),
|
|
RemoteChainID: binary.BigEndian.Uint32(si[4:8]),
|
|
AgreementType: state.AgreementType(si[8]),
|
|
Status: state.AgreementStatus(si[9]),
|
|
Terms: terms,
|
|
EffectiveDate: binary.BigEndian.Uint32(si[42:46]),
|
|
ExpirationDate: binary.BigEndian.Uint32(si[46:50]),
|
|
CreatedAt: binary.BigEndian.Uint32(si[50:54]),
|
|
UpdatedAt: binary.BigEndian.Uint32(si[54:58]),
|
|
}, true
|
|
}
|
|
|
|
func (p *Pons) setAgreementInternal(d *dao.Simple, agr *state.BilateralAgreement) {
|
|
buf := make([]byte, 58)
|
|
binary.BigEndian.PutUint32(buf[0:4], agr.LocalChainID)
|
|
binary.BigEndian.PutUint32(buf[4:8], agr.RemoteChainID)
|
|
buf[8] = byte(agr.AgreementType)
|
|
buf[9] = byte(agr.Status)
|
|
copy(buf[10:42], agr.Terms[:])
|
|
binary.BigEndian.PutUint32(buf[42:46], agr.EffectiveDate)
|
|
binary.BigEndian.PutUint32(buf[46:50], agr.ExpirationDate)
|
|
binary.BigEndian.PutUint32(buf[50:54], agr.CreatedAt)
|
|
binary.BigEndian.PutUint32(buf[54:58], agr.UpdatedAt)
|
|
d.PutStorageItem(p.ID, makePonsAgreementKey(agr.ID), buf)
|
|
// Index by chain
|
|
d.PutStorageItem(p.ID, makePonsAgreementByChainKey(agr.RemoteChainID, agr.ID), []byte{1})
|
|
}
|
|
|
|
// Verification storage format:
|
|
// requestingChain(4) + targetChain(4) + subject(20) + verificationType(1) + dataHash(32) +
|
|
// status(1) + responseHash(32) + requester(20) + createdAt(4) + expiresAt(4) + respondedAt(4) = 126 bytes
|
|
func (p *Pons) getVerificationInternal(d *dao.Simple, requestID uint64) (*state.VerificationRequest, bool) {
|
|
si := d.GetStorageItem(p.ID, makePonsVerificationKey(requestID))
|
|
if si == nil || len(si) < 126 {
|
|
return nil, false
|
|
}
|
|
|
|
var subject util.Uint160
|
|
copy(subject[:], si[8:28])
|
|
var dataHash util.Uint256
|
|
copy(dataHash[:], si[29:61])
|
|
var responseHash util.Uint256
|
|
copy(responseHash[:], si[62:94])
|
|
var requester util.Uint160
|
|
copy(requester[:], si[94:114])
|
|
|
|
return &state.VerificationRequest{
|
|
ID: requestID,
|
|
RequestingChain: binary.BigEndian.Uint32(si[0:4]),
|
|
TargetChain: binary.BigEndian.Uint32(si[4:8]),
|
|
Subject: subject,
|
|
VerificationType: state.VerificationType(si[28]),
|
|
DataHash: dataHash,
|
|
Status: state.VerificationStatus(si[61]),
|
|
ResponseHash: responseHash,
|
|
Requester: requester,
|
|
CreatedAt: binary.BigEndian.Uint32(si[114:118]),
|
|
ExpiresAt: binary.BigEndian.Uint32(si[118:122]),
|
|
RespondedAt: binary.BigEndian.Uint32(si[122:126]),
|
|
}, true
|
|
}
|
|
|
|
func (p *Pons) setVerificationInternal(d *dao.Simple, vr *state.VerificationRequest) {
|
|
buf := make([]byte, 126)
|
|
binary.BigEndian.PutUint32(buf[0:4], vr.RequestingChain)
|
|
binary.BigEndian.PutUint32(buf[4:8], vr.TargetChain)
|
|
copy(buf[8:28], vr.Subject[:])
|
|
buf[28] = byte(vr.VerificationType)
|
|
copy(buf[29:61], vr.DataHash[:])
|
|
buf[61] = byte(vr.Status)
|
|
copy(buf[62:94], vr.ResponseHash[:])
|
|
copy(buf[94:114], vr.Requester[:])
|
|
binary.BigEndian.PutUint32(buf[114:118], vr.CreatedAt)
|
|
binary.BigEndian.PutUint32(buf[118:122], vr.ExpiresAt)
|
|
binary.BigEndian.PutUint32(buf[122:126], vr.RespondedAt)
|
|
d.PutStorageItem(p.ID, makePonsVerificationKey(vr.ID), buf)
|
|
// Index by subject
|
|
d.PutStorageItem(p.ID, makePonsVerifBySubjectKey(vr.Subject, vr.ID), []byte{1})
|
|
}
|
|
|
|
// Settlement storage format:
|
|
// fromChain(4) + toChain(4) + sender(20) + receiver(20) + amount(8) + status(1) +
|
|
// createdAt(4) + settledAt(4) + txHash(32) + refLen(2) + reference(var) = 99 + ref bytes
|
|
func (p *Pons) getSettlementInternal(d *dao.Simple, settlementID uint64) (*state.SettlementRequest, bool) {
|
|
si := d.GetStorageItem(p.ID, makePonsSettlementKey(settlementID))
|
|
if si == nil || len(si) < 99 {
|
|
return nil, false
|
|
}
|
|
|
|
var sender util.Uint160
|
|
copy(sender[:], si[8:28])
|
|
var receiver util.Uint160
|
|
copy(receiver[:], si[28:48])
|
|
var txHash util.Uint256
|
|
copy(txHash[:], si[65:97])
|
|
|
|
refLen := binary.BigEndian.Uint16(si[97:99])
|
|
var reference string
|
|
if len(si) >= 99+int(refLen) {
|
|
reference = string(si[99 : 99+refLen])
|
|
}
|
|
|
|
return &state.SettlementRequest{
|
|
ID: settlementID,
|
|
FromChain: binary.BigEndian.Uint32(si[0:4]),
|
|
ToChain: binary.BigEndian.Uint32(si[4:8]),
|
|
Sender: sender,
|
|
Receiver: receiver,
|
|
Amount: binary.BigEndian.Uint64(si[48:56]),
|
|
Status: state.SettlementStatus(si[56]),
|
|
CreatedAt: binary.BigEndian.Uint32(si[57:61]),
|
|
SettledAt: binary.BigEndian.Uint32(si[61:65]),
|
|
TxHash: txHash,
|
|
Reference: reference,
|
|
}, true
|
|
}
|
|
|
|
func (p *Pons) setSettlementInternal(d *dao.Simple, sr *state.SettlementRequest) {
|
|
refBytes := []byte(sr.Reference)
|
|
buf := make([]byte, 99+len(refBytes))
|
|
binary.BigEndian.PutUint32(buf[0:4], sr.FromChain)
|
|
binary.BigEndian.PutUint32(buf[4:8], sr.ToChain)
|
|
copy(buf[8:28], sr.Sender[:])
|
|
copy(buf[28:48], sr.Receiver[:])
|
|
binary.BigEndian.PutUint64(buf[48:56], sr.Amount)
|
|
buf[56] = byte(sr.Status)
|
|
binary.BigEndian.PutUint32(buf[57:61], sr.CreatedAt)
|
|
binary.BigEndian.PutUint32(buf[61:65], sr.SettledAt)
|
|
copy(buf[65:97], sr.TxHash[:])
|
|
binary.BigEndian.PutUint16(buf[97:99], uint16(len(refBytes)))
|
|
copy(buf[99:], refBytes)
|
|
d.PutStorageItem(p.ID, makePonsSettlementKey(sr.ID), buf)
|
|
// Index by chain
|
|
d.PutStorageItem(p.ID, makePonsSettlByChainKey(sr.ToChain, sr.ID), []byte{1})
|
|
}
|
|
|
|
// CredentialShare storage format:
|
|
// sourceChain(4) + targetChain(4) + owner(20) + credentialType(1) + credentialID(8) +
|
|
// contentHash(32) + validUntil(4) + createdAt(4) + isRevoked(1) = 78 bytes
|
|
func (p *Pons) getCredentialShareInternal(d *dao.Simple, shareID uint64) (*state.CredentialShare, bool) {
|
|
si := d.GetStorageItem(p.ID, makePonsCredentialKey(shareID))
|
|
if si == nil || len(si) < 78 {
|
|
return nil, false
|
|
}
|
|
|
|
var owner util.Uint160
|
|
copy(owner[:], si[8:28])
|
|
var contentHash util.Uint256
|
|
copy(contentHash[:], si[37:69])
|
|
|
|
return &state.CredentialShare{
|
|
ID: shareID,
|
|
SourceChain: binary.BigEndian.Uint32(si[0:4]),
|
|
TargetChain: binary.BigEndian.Uint32(si[4:8]),
|
|
Owner: owner,
|
|
CredentialType: state.VerificationType(si[28]),
|
|
CredentialID: binary.BigEndian.Uint64(si[29:37]),
|
|
ContentHash: contentHash,
|
|
ValidUntil: binary.BigEndian.Uint32(si[69:73]),
|
|
CreatedAt: binary.BigEndian.Uint32(si[73:77]),
|
|
IsRevoked: si[77] != 0,
|
|
}, true
|
|
}
|
|
|
|
func (p *Pons) setCredentialShareInternal(d *dao.Simple, cs *state.CredentialShare) {
|
|
buf := make([]byte, 78)
|
|
binary.BigEndian.PutUint32(buf[0:4], cs.SourceChain)
|
|
binary.BigEndian.PutUint32(buf[4:8], cs.TargetChain)
|
|
copy(buf[8:28], cs.Owner[:])
|
|
buf[28] = byte(cs.CredentialType)
|
|
binary.BigEndian.PutUint64(buf[29:37], cs.CredentialID)
|
|
copy(buf[37:69], cs.ContentHash[:])
|
|
binary.BigEndian.PutUint32(buf[69:73], cs.ValidUntil)
|
|
binary.BigEndian.PutUint32(buf[73:77], cs.CreatedAt)
|
|
if cs.IsRevoked {
|
|
buf[77] = 1
|
|
}
|
|
d.PutStorageItem(p.ID, makePonsCredentialKey(cs.ID), buf)
|
|
// Index by owner
|
|
d.PutStorageItem(p.ID, makePonsCredByOwnerKey(cs.Owner, cs.ID), []byte{1})
|
|
}
|
|
|
|
// hasActiveAgreementInternal checks if there's an active agreement with the target chain
|
|
// for the specified agreement type.
|
|
func (p *Pons) hasActiveAgreementInternal(d *dao.Simple, remoteChainID uint32, agreementType state.AgreementType, blockHeight uint32) bool {
|
|
count := p.getCounterInternal(d, makePonsAgreementCounterKey())
|
|
for i := uint64(1); i <= count; i++ {
|
|
agr, exists := p.getAgreementInternal(d, i)
|
|
if !exists {
|
|
continue
|
|
}
|
|
if agr.RemoteChainID != remoteChainID {
|
|
continue
|
|
}
|
|
if agr.Status != state.AgreementActive {
|
|
continue
|
|
}
|
|
// Check expiration
|
|
if agr.ExpirationDate > 0 && agr.ExpirationDate < blockHeight {
|
|
continue
|
|
}
|
|
// Check if agreement type matches or is comprehensive
|
|
if agr.AgreementType == agreementType || agr.AgreementType == state.AgreementTypeComprehensive {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ============================================================================
|
|
// Contract Methods
|
|
// ============================================================================
|
|
|
|
func (p *Pons) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(cfg.LocalChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cfg.VerificationTimeout))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cfg.SettlementTimeout))),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MaxPendingRequests)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cfg.CredentialShareExpiry))),
|
|
})
|
|
}
|
|
|
|
func (p *Pons) setLocalChainID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
chainID := uint32(toBigInt(args[0]).Int64())
|
|
|
|
if !p.NEO.CheckCommittee(ic) {
|
|
panic("only committee can set chain ID")
|
|
}
|
|
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
cfg.LocalChainID = chainID
|
|
p.setConfigInternal(ic.DAO, cfg)
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) createAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
remoteChainID := uint32(toBigInt(args[0]).Int64())
|
|
agreementType := state.AgreementType(toBigInt(args[1]).Int64())
|
|
termsHashBytes, err := args[2].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
termsHash, err := util.Uint256DecodeBytesBE(termsHashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
expirationHeight := uint32(toBigInt(args[3]).Int64())
|
|
|
|
if !p.NEO.CheckCommittee(ic) {
|
|
panic("only committee can create agreements")
|
|
}
|
|
|
|
if agreementType > state.AgreementTypeComprehensive {
|
|
panic(ErrInvalidAgreementType)
|
|
}
|
|
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
|
|
// Create agreement
|
|
agreementID := p.incrementCounterInternal(ic.DAO, makePonsAgreementCounterKey())
|
|
agr := &state.BilateralAgreement{
|
|
ID: agreementID,
|
|
LocalChainID: cfg.LocalChainID,
|
|
RemoteChainID: remoteChainID,
|
|
AgreementType: agreementType,
|
|
Status: state.AgreementPending,
|
|
Terms: termsHash,
|
|
EffectiveDate: 0, // Set when activated
|
|
ExpirationDate: expirationHeight,
|
|
CreatedAt: ic.Block.Index,
|
|
UpdatedAt: ic.Block.Index,
|
|
}
|
|
p.setAgreementInternal(ic.DAO, agr)
|
|
|
|
ic.AddNotification(p.Hash, AgreementCreatedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(remoteChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agreementType))),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID))
|
|
}
|
|
|
|
func (p *Pons) updateAgreementStatus(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
agreementID := toBigInt(args[0]).Uint64()
|
|
newStatus := state.AgreementStatus(toBigInt(args[1]).Int64())
|
|
|
|
if !p.NEO.CheckCommittee(ic) {
|
|
panic("only committee can update agreement status")
|
|
}
|
|
|
|
agr, exists := p.getAgreementInternal(ic.DAO, agreementID)
|
|
if !exists {
|
|
panic(ErrAgreementNotFound)
|
|
}
|
|
|
|
oldStatus := agr.Status
|
|
agr.Status = newStatus
|
|
agr.UpdatedAt = ic.Block.Index
|
|
|
|
// Set effective date when activating
|
|
if newStatus == state.AgreementActive && oldStatus != state.AgreementActive {
|
|
agr.EffectiveDate = ic.Block.Index
|
|
}
|
|
|
|
p.setAgreementInternal(ic.DAO, agr)
|
|
|
|
ic.AddNotification(p.Hash, AgreementUpdatedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(oldStatus))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(newStatus))),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) getAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
agreementID := toBigInt(args[0]).Uint64()
|
|
|
|
agr, exists := p.getAgreementInternal(ic.DAO, agreementID)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(agr.ID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.LocalChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.RemoteChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.AgreementType))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.Status))),
|
|
stackitem.NewByteArray(agr.Terms.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.EffectiveDate))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.ExpirationDate))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(agr.UpdatedAt))),
|
|
})
|
|
}
|
|
|
|
func (p *Pons) hasActiveAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
remoteChainID := uint32(toBigInt(args[0]).Int64())
|
|
agreementType := state.AgreementType(toBigInt(args[1]).Int64())
|
|
|
|
has := p.hasActiveAgreementInternal(ic.DAO, remoteChainID, agreementType, ic.Block.Index)
|
|
return stackitem.NewBool(has)
|
|
}
|
|
|
|
func (p *Pons) requestVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
targetChainID := uint32(toBigInt(args[0]).Int64())
|
|
subject := toUint160(args[1])
|
|
verificationType := state.VerificationType(toBigInt(args[2]).Int64())
|
|
dataHashBytes, err := args[3].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dataHash, err := util.Uint256DecodeBytesBE(dataHashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
|
|
// Check for active agreement
|
|
agreementType := state.AgreementTypeIdentity
|
|
switch verificationType {
|
|
case state.VerificationTypeCredential, state.VerificationTypeCertificate:
|
|
agreementType = state.AgreementTypeEducation
|
|
case state.VerificationTypeHealth:
|
|
agreementType = state.AgreementTypeHealthcare
|
|
}
|
|
|
|
if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) {
|
|
panic(ErrNoAgreement)
|
|
}
|
|
|
|
// Get requester from caller
|
|
requester := ic.VM.GetCallingScriptHash()
|
|
|
|
// Create verification request
|
|
requestID := p.incrementCounterInternal(ic.DAO, makePonsVerificationCounterKey())
|
|
vr := &state.VerificationRequest{
|
|
ID: requestID,
|
|
RequestingChain: cfg.LocalChainID,
|
|
TargetChain: targetChainID,
|
|
Subject: subject,
|
|
VerificationType: verificationType,
|
|
DataHash: dataHash,
|
|
Status: state.VerificationPending,
|
|
Requester: requester,
|
|
CreatedAt: ic.Block.Index,
|
|
ExpiresAt: ic.Block.Index + cfg.VerificationTimeout,
|
|
}
|
|
p.setVerificationInternal(ic.DAO, vr)
|
|
|
|
ic.AddNotification(p.Hash, VerificationRequestedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(targetChainID))),
|
|
stackitem.NewByteArray(subject.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(verificationType))),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(requestID))
|
|
}
|
|
|
|
func (p *Pons) respondVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
requestID := toBigInt(args[0]).Uint64()
|
|
approved := toBool(args[1])
|
|
responseHashBytes, err := args[2].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
responseHash, err := util.Uint256DecodeBytesBE(responseHashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if !p.NEO.CheckCommittee(ic) {
|
|
panic("only committee can respond to verification requests")
|
|
}
|
|
|
|
vr, exists := p.getVerificationInternal(ic.DAO, requestID)
|
|
if !exists {
|
|
panic(ErrVerificationNotFound)
|
|
}
|
|
|
|
if vr.Status != state.VerificationPending {
|
|
panic("verification request already processed")
|
|
}
|
|
|
|
if ic.Block.Index > vr.ExpiresAt {
|
|
vr.Status = state.VerificationExpired
|
|
p.setVerificationInternal(ic.DAO, vr)
|
|
panic(ErrVerificationExpired)
|
|
}
|
|
|
|
if approved {
|
|
vr.Status = state.VerificationApproved
|
|
} else {
|
|
vr.Status = state.VerificationRejected
|
|
}
|
|
vr.ResponseHash = responseHash
|
|
vr.RespondedAt = ic.Block.Index
|
|
p.setVerificationInternal(ic.DAO, vr)
|
|
|
|
ic.AddNotification(p.Hash, VerificationRespondedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)),
|
|
stackitem.NewBool(approved),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) getVerificationRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
requestID := toBigInt(args[0]).Uint64()
|
|
|
|
vr, exists := p.getVerificationInternal(ic.DAO, requestID)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(vr.ID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.RequestingChain))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.TargetChain))),
|
|
stackitem.NewByteArray(vr.Subject.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.VerificationType))),
|
|
stackitem.NewByteArray(vr.DataHash.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.Status))),
|
|
stackitem.NewByteArray(vr.ResponseHash.BytesBE()),
|
|
stackitem.NewByteArray(vr.Requester.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.ExpiresAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(vr.RespondedAt))),
|
|
})
|
|
}
|
|
|
|
func (p *Pons) requestSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
toChainID := uint32(toBigInt(args[0]).Int64())
|
|
receiver := toUint160(args[1])
|
|
amount := toBigInt(args[2]).Uint64()
|
|
reference := toString(args[3])
|
|
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
|
|
// Check for settlement agreement
|
|
if !p.hasActiveAgreementInternal(ic.DAO, toChainID, state.AgreementTypeSettlement, ic.Block.Index) {
|
|
panic(ErrNoAgreement)
|
|
}
|
|
|
|
sender := ic.VM.GetCallingScriptHash()
|
|
|
|
// Create settlement request
|
|
settlementID := p.incrementCounterInternal(ic.DAO, makePonsSettlementCounterKey())
|
|
sr := &state.SettlementRequest{
|
|
ID: settlementID,
|
|
FromChain: cfg.LocalChainID,
|
|
ToChain: toChainID,
|
|
Sender: sender,
|
|
Receiver: receiver,
|
|
Amount: amount,
|
|
Status: state.SettlementPending,
|
|
Reference: reference,
|
|
CreatedAt: ic.Block.Index,
|
|
}
|
|
p.setSettlementInternal(ic.DAO, sr)
|
|
|
|
ic.AddNotification(p.Hash, SettlementRequestedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(toChainID))),
|
|
stackitem.NewByteArray(receiver.BytesBE()),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(amount)),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID))
|
|
}
|
|
|
|
func (p *Pons) completeSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
settlementID := toBigInt(args[0]).Uint64()
|
|
txHashBytes, err := args[1].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
txHash, err := util.Uint256DecodeBytesBE(txHashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if !p.NEO.CheckCommittee(ic) {
|
|
panic("only committee can complete settlements")
|
|
}
|
|
|
|
sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
|
|
if !exists {
|
|
panic(ErrSettlementNotFound)
|
|
}
|
|
|
|
if sr.Status != state.SettlementPending {
|
|
panic("settlement already processed")
|
|
}
|
|
|
|
sr.Status = state.SettlementCompleted
|
|
sr.SettledAt = ic.Block.Index
|
|
sr.TxHash = txHash
|
|
p.setSettlementInternal(ic.DAO, sr)
|
|
|
|
ic.AddNotification(p.Hash, SettlementCompletedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)),
|
|
stackitem.NewByteArray(txHash.BytesBE()),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) cancelSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
settlementID := toBigInt(args[0]).Uint64()
|
|
|
|
sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
|
|
if !exists {
|
|
panic(ErrSettlementNotFound)
|
|
}
|
|
|
|
if sr.Status != state.SettlementPending {
|
|
panic("settlement already processed")
|
|
}
|
|
|
|
// Allow sender or committee to cancel
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
if !caller.Equals(sr.Sender) && !p.NEO.CheckCommittee(ic) {
|
|
panic("only sender or committee can cancel settlement")
|
|
}
|
|
|
|
sr.Status = state.SettlementCancelled
|
|
p.setSettlementInternal(ic.DAO, sr)
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) getSettlementRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
settlementID := toBigInt(args[0]).Uint64()
|
|
|
|
sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(sr.ID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(sr.FromChain))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(sr.ToChain))),
|
|
stackitem.NewByteArray(sr.Sender.BytesBE()),
|
|
stackitem.NewByteArray(sr.Receiver.BytesBE()),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(sr.Amount)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(sr.Status))),
|
|
stackitem.NewByteArray(sr.TxHash.BytesBE()),
|
|
stackitem.NewByteArray([]byte(sr.Reference)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(sr.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(sr.SettledAt))),
|
|
})
|
|
}
|
|
|
|
func (p *Pons) shareCredential(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
targetChainID := uint32(toBigInt(args[0]).Int64())
|
|
credentialType := state.VerificationType(toBigInt(args[1]).Int64())
|
|
credentialID := toBigInt(args[2]).Uint64()
|
|
contentHashBytes, err := args[3].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
contentHash, err := util.Uint256DecodeBytesBE(contentHashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
validUntil := uint32(toBigInt(args[4]).Int64())
|
|
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
|
|
// Determine agreement type needed
|
|
agreementType := state.AgreementTypeEducation
|
|
if credentialType == state.VerificationTypeHealth {
|
|
agreementType = state.AgreementTypeHealthcare
|
|
}
|
|
|
|
if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) {
|
|
panic(ErrNoAgreement)
|
|
}
|
|
|
|
owner := ic.VM.GetCallingScriptHash()
|
|
|
|
// Set default validity if not provided
|
|
if validUntil == 0 {
|
|
validUntil = ic.Block.Index + cfg.CredentialShareExpiry
|
|
}
|
|
|
|
// Create credential share
|
|
shareID := p.incrementCounterInternal(ic.DAO, makePonsCredentialCounterKey())
|
|
cs := &state.CredentialShare{
|
|
ID: shareID,
|
|
SourceChain: cfg.LocalChainID,
|
|
TargetChain: targetChainID,
|
|
Owner: owner,
|
|
CredentialType: credentialType,
|
|
CredentialID: credentialID,
|
|
ContentHash: contentHash,
|
|
ValidUntil: validUntil,
|
|
CreatedAt: ic.Block.Index,
|
|
IsRevoked: false,
|
|
}
|
|
p.setCredentialShareInternal(ic.DAO, cs)
|
|
|
|
ic.AddNotification(p.Hash, CredentialSharedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)),
|
|
stackitem.NewByteArray(owner.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(targetChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(credentialType))),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(shareID))
|
|
}
|
|
|
|
func (p *Pons) revokeCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
shareID := toBigInt(args[0]).Uint64()
|
|
|
|
cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
|
|
if !exists {
|
|
panic(ErrCredentialNotFound)
|
|
}
|
|
|
|
// Allow owner or committee to revoke
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
if !caller.Equals(cs.Owner) && !p.NEO.CheckCommittee(ic) {
|
|
panic(ErrNotCredentialOwner)
|
|
}
|
|
|
|
cs.IsRevoked = true
|
|
p.setCredentialShareInternal(ic.DAO, cs)
|
|
|
|
ic.AddNotification(p.Hash, CredentialRevokedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)),
|
|
stackitem.NewByteArray(cs.Owner.BytesBE()),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) getCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
shareID := toBigInt(args[0]).Uint64()
|
|
|
|
cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(cs.ID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cs.SourceChain))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cs.TargetChain))),
|
|
stackitem.NewByteArray(cs.Owner.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cs.CredentialType))),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(cs.CredentialID)),
|
|
stackitem.NewByteArray(cs.ContentHash.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cs.ValidUntil))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cs.CreatedAt))),
|
|
stackitem.NewBool(cs.IsRevoked),
|
|
})
|
|
}
|
|
|
|
func (p *Pons) verifyCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
shareID := toBigInt(args[0]).Uint64()
|
|
|
|
cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
|
|
if !exists {
|
|
return stackitem.NewBool(false)
|
|
}
|
|
|
|
if cs.IsRevoked {
|
|
return stackitem.NewBool(false)
|
|
}
|
|
|
|
if ic.Block.Index > cs.ValidUntil {
|
|
return stackitem.NewBool(false)
|
|
}
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Pons) getAgreementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
count := p.getCounterInternal(ic.DAO, makePonsAgreementCounterKey())
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
|
}
|
|
|
|
func (p *Pons) getVerificationCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
count := p.getCounterInternal(ic.DAO, makePonsVerificationCounterKey())
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
|
}
|
|
|
|
func (p *Pons) getSettlementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
count := p.getCounterInternal(ic.DAO, makePonsSettlementCounterKey())
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
|
}
|
|
|
|
func (p *Pons) getCredentialShareCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
count := p.getCounterInternal(ic.DAO, makePonsCredentialCounterKey())
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
|
|
}
|