Files
MAcode/tcs-iptv/contracts/tcs_registry/registry.go
T

390 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package main 是 TCS-IPTV 可信数据空间的 ChainMaker 智能合约(Go)。
//
// 本合约为独立合约模块(独立 go.mod),按 ChainMaker docker-go 合约规范部署。
// 与 internal/chain.Client 接口语义一一对应;MVP/二期用 MemoryChain 等价实现,
// 具备链环境后部署本合约,由 chain-svc / ChainMakerClient 通过 SDK 调用替换内存实现。
//
// 状态键设计(KV):
//
// content:{ccCode} -> Content JSON(含 status
// binding:{ccCode}:0 -> 整剧 file 哈希绑定 JSON
// binding:{ccCode}:p -> 感知哈希绑定 JSON
// ep:{ccCode}:{n} -> 集级绑定 JSON {episode,hash,revoked,reason}
// epcount:{ccCode} -> 集数 N
// hashidx:{fileHash} -> ccCode(防换壳重发)
// mapping:{ccCode}:{idx} -> Mapping JSON
// mapcount:{ccCode} -> 映射数 N
// version:{ccCode}:{idx} -> VersionChange JSON
// vercount:{ccCode} -> 版本变更数 N
// ctid2ma:{ctid} -> ccCode
// allcccodes -> []ccCode(供 ListContents 遍历)
//
// 权限:通过 sender 组织证书判断(仅监管组织可 IssueMA/Revoke/RevokeEpisode/Restore)。
package main
import (
"encoding/json"
"errors"
"strconv"
"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo"
"chainmaker.org/chainmaker/contract-sdk-go/v2/sandbox"
"chainmaker.org/chainmaker/contract-sdk-go/v2/sdk"
)
// TCSRegistry 合约。
type TCSRegistry struct{}
const (
orgRegulator = "regulator" // 监管组织:仅其可签发/下架
orgReviewer = "reviewer" // 审核组织:哈希绑定/版本变更/状态流转
)
// InitContract 合约初始化。
func (t *TCSRegistry) InitContract() protogo.Response {
return sdk.Success([]byte("tcs_registry initialized"))
}
// UpgradeContract 合约升级。
func (t *TCSRegistry) UpgradeContract() protogo.Response {
return sdk.Success([]byte("tcs_registry upgraded"))
}
func senderOrg() string {
org, _ := sdk.Instance.GetSenderOrgId()
return org
}
func getInt(key, field string) int {
v, _ := sdk.Instance.GetStateByte(key, field)
if len(v) == 0 {
return 0
}
n, _ := strconv.Atoi(string(v))
return n
}
func putInt(key, field string, n int) {
_ = sdk.Instance.PutStateByte(key, field, []byte(strconv.Itoa(n)))
}
// epBinding 集级绑定的链上结构。
type epBinding struct {
Episode int `json:"episode"`
HashValue string `json:"hash_value"`
Revoked bool `json:"revoked"`
Reason string `json:"revoked_reason,omitempty"`
}
// appendCCCode 把新发码加入全局列表(供 ListContents 遍历)。
func appendCCCode(ccCode string) {
var all []string
if v, _ := sdk.Instance.GetStateByte("allcccodes", ""); len(v) > 0 {
_ = json.Unmarshal(v, &all)
}
all = append(all, ccCode)
b, _ := json.Marshal(all)
_ = sdk.Instance.PutStateByte("allcccodes", "", b)
}
// ---- 写方法 ----
// IssueMA 签发 MA 码并 1:1 强绑定哈希;同时登记集级哈希(仅监管组织)。
func (t *TCSRegistry) IssueMA() protogo.Response {
if senderOrg() != orgRegulator {
return sdk.Error("permission denied: only regulator can issue MA")
}
args := sdk.Instance.GetArgs()
ccCode := string(args["ma_code"])
ctid := string(args["ctid"])
fileHash := string(args["file_hash"])
if existing, _ := sdk.Instance.GetStateByte("content", ccCode); len(existing) > 0 {
return sdk.Error("MA already issued (1:1 binding immutable)")
}
if bound, _ := sdk.Instance.GetStateByte("hashidx", fileHash); len(bound) > 0 {
return sdk.Error("content hash already exists")
}
// 内容主记录(强制 status=approved,与 MemoryChain 一致)
var content map[string]interface{}
_ = json.Unmarshal(args["content"], &content)
if content == nil {
content = map[string]interface{}{}
}
content["ma_code"] = ccCode
content["content_twin_id"] = ctid
content["status"] = "approved"
cj, _ := json.Marshal(content)
_ = sdk.Instance.PutStateByte("content", ccCode, cj)
// 整剧 file 绑定 + 感知哈希绑定
fb, _ := json.Marshal(epBinding{Episode: 0, HashValue: fileHash})
_ = sdk.Instance.PutStateByte("binding", ccCode+":0", fb)
if ph := string(args["perceptual_hash"]); ph != "" {
pb, _ := json.Marshal(map[string]string{"hash_type": "perceptual", "hash_value": ph})
_ = sdk.Instance.PutStateByte("binding", ccCode+":p", pb)
}
_ = sdk.Instance.PutStateByte("hashidx", fileHash, []byte(ccCode))
_ = sdk.Instance.PutStateByte("ctid2ma", ctid, []byte(ccCode))
// 集级哈希
var eps []map[string]interface{}
_ = json.Unmarshal(args["episodes"], &eps)
n := 0
for _, e := range eps {
ep := int(toFloat(e["episode"]))
hv, _ := e["file_sha256"].(string)
if ep <= 0 || hv == "" {
continue
}
eb, _ := json.Marshal(epBinding{Episode: ep, HashValue: hv})
_ = sdk.Instance.PutStateByte("ep", ccCode+":"+strconv.Itoa(ep), eb)
if exist, _ := sdk.Instance.GetStateByte("hashidx", hv); len(exist) == 0 {
_ = sdk.Instance.PutStateByte("hashidx", hv, []byte(ccCode))
}
if ep > n {
n = ep
}
}
putInt("epcount", ccCode, n)
appendCCCode(ccCode)
sdk.Instance.EmitEvent("RegisterSuccess", []string{ccCode, fileHash})
return sdk.Success([]byte(ccCode))
}
func toFloat(v interface{}) float64 {
if f, ok := v.(float64); ok {
return f
}
return 0
}
// RegisterHashBinding 追加哈希绑定(如转码版)。MA 必须已签发(审核/监管)。
func (t *TCSRegistry) RegisterHashBinding() protogo.Response {
if o := senderOrg(); o != orgReviewer && o != orgRegulator {
return sdk.Error("permission denied")
}
args := sdk.Instance.GetArgs()
ctid := string(args["ctid"])
ma, _ := sdk.Instance.GetStateByte("ctid2ma", ctid)
if len(ma) == 0 {
return sdk.Error("MA not issued")
}
idx := getInt("bindextra", string(ma)) + 1
_ = sdk.Instance.PutStateByte("bindextra", string(ma)+":"+strconv.Itoa(idx), args["binding"])
putInt("bindextra", string(ma), idx)
return sdk.Success([]byte("ok"))
}
// RegisterMapping 注册三方编码映射;MA 必须已签发(任意角色注册本方)。
func (t *TCSRegistry) RegisterMapping() protogo.Response {
args := sdk.Instance.GetArgs()
ctid := string(args["ctid"])
ma, _ := sdk.Instance.GetStateByte("ctid2ma", ctid)
if len(ma) == 0 {
return sdk.Error("MA not issued")
}
idx := getInt("mapcount", string(ma)) + 1
_ = sdk.Instance.PutStateByte("mapping", string(ma)+":"+strconv.Itoa(idx), args["mapping"])
putInt("mapcount", string(ma), idx)
return sdk.Success([]byte("ok"))
}
// RecordVersionChange 记录版本变更(审核/监管)。
func (t *TCSRegistry) RecordVersionChange() protogo.Response {
if o := senderOrg(); o != orgReviewer && o != orgRegulator {
return sdk.Error("permission denied")
}
args := sdk.Instance.GetArgs()
ctid := string(args["ctid"])
ma, _ := sdk.Instance.GetStateByte("ctid2ma", ctid)
if len(ma) == 0 {
return sdk.Error("MA not issued")
}
idx := getInt("vercount", string(ma)) + 1
_ = sdk.Instance.PutStateByte("version", string(ma)+":"+strconv.Itoa(idx), args["vc"])
putInt("vercount", string(ma), idx)
return sdk.Success([]byte("ok"))
}
// Revoke 整剧下架(仅监管组织)。
func (t *TCSRegistry) Revoke() protogo.Response {
if senderOrg() != orgRegulator {
return sdk.Error("permission denied: only regulator can revoke")
}
return setStatus(string(sdk.Instance.GetArgs()["ma_code"]), "revoked", "Revoked")
}
// SetContentStatus 状态流转(审核/监管):入库/发布等。
func (t *TCSRegistry) SetContentStatus() protogo.Response {
if o := senderOrg(); o != orgReviewer && o != orgRegulator {
return sdk.Error("permission denied")
}
args := sdk.Instance.GetArgs()
return setStatus(string(args["ma_code"]), string(args["status"]), "StatusChanged")
}
func setStatus(ccCode, status, event string) protogo.Response {
cj, _ := sdk.Instance.GetStateByte("content", ccCode)
if len(cj) == 0 {
return sdk.Error("not found")
}
var content map[string]interface{}
_ = json.Unmarshal(cj, &content)
content["status"] = status
nj, _ := json.Marshal(content)
_ = sdk.Instance.PutStateByte("content", ccCode, nj)
sdk.Instance.EmitEvent(event, []string{ccCode, status})
return sdk.Success([]byte("ok"))
}
// RevokeEpisode 集级下架(仅监管组织)。
func (t *TCSRegistry) RevokeEpisode() protogo.Response {
if senderOrg() != orgRegulator {
return sdk.Error("permission denied")
}
args := sdk.Instance.GetArgs()
return setEpisodeRevoked(string(args["ma_code"]), string(args["episode"]), true, string(args["reason"]))
}
// RestoreEpisode 集级恢复(仅监管组织)。
func (t *TCSRegistry) RestoreEpisode() protogo.Response {
if senderOrg() != orgRegulator {
return sdk.Error("permission denied")
}
args := sdk.Instance.GetArgs()
return setEpisodeRevoked(string(args["ma_code"]), string(args["episode"]), false, "")
}
// Restore 整剧恢复(仅监管组织)。
func (t *TCSRegistry) Restore() protogo.Response {
if senderOrg() != orgRegulator {
return sdk.Error("permission denied")
}
return setStatus(string(sdk.Instance.GetArgs()["ma_code"]), "published", "Restored")
}
func setEpisodeRevoked(ccCode, epStr string, revoked bool, reason string) protogo.Response {
key := ccCode + ":" + epStr
v, _ := sdk.Instance.GetStateByte("ep", key)
if len(v) == 0 {
return sdk.Error("not found")
}
var eb epBinding
_ = json.Unmarshal(v, &eb)
eb.Revoked = revoked
eb.Reason = reason
nb, _ := json.Marshal(eb)
_ = sdk.Instance.PutStateByte("ep", key, nb)
return sdk.Success([]byte("ok"))
}
// ---- 读方法 ----
// VerifyHash 校验整剧/转码哈希是否绑定到该 MA。
func (t *TCSRegistry) VerifyHash() protogo.Response {
args := sdk.Instance.GetArgs()
bound, _ := sdk.Instance.GetStateByte("hashidx", string(args["file_hash"]))
if string(bound) == string(args["ma_code"]) {
return sdk.Success([]byte("true"))
}
return sdk.Success([]byte("false"))
}
// VerifyEpisodeHash 校验某集哈希。
func (t *TCSRegistry) VerifyEpisodeHash() protogo.Response {
args := sdk.Instance.GetArgs()
v, _ := sdk.Instance.GetStateByte("ep", string(args["ma_code"])+":"+string(args["episode"]))
if len(v) == 0 {
return sdk.Success([]byte("false"))
}
var eb epBinding
_ = json.Unmarshal(v, &eb)
if eb.HashValue == string(args["file_hash"]) {
return sdk.Success([]byte("true"))
}
return sdk.Success([]byte("false"))
}
// HashExists 返回绑定的 MA(不存在返回空)。
func (t *TCSRegistry) HashExists() protogo.Response {
bound, _ := sdk.Instance.GetStateByte("hashidx", string(sdk.Instance.GetArgs()["file_hash"]))
return sdk.Success(bound)
}
// ListEpisodes 返回某 MA 的集级绑定数组(JSON)。
func (t *TCSRegistry) ListEpisodes() protogo.Response {
ccCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("epcount", ccCode)
out := make([]epBinding, 0, n)
for i := 1; i <= n; i++ {
if v, _ := sdk.Instance.GetStateByte("ep", ccCode+":"+strconv.Itoa(i)); len(v) > 0 {
var eb epBinding
_ = json.Unmarshal(v, &eb)
out = append(out, eb)
}
}
b, _ := json.Marshal(out)
return sdk.Success(b)
}
// QueryContent 查询内容主记录。
func (t *TCSRegistry) QueryContent() protogo.Response {
v, _ := sdk.Instance.GetStateByte("content", string(sdk.Instance.GetArgs()["ma_code"]))
if len(v) == 0 {
return sdk.Error("not found")
}
return sdk.Success(v)
}
// QueryMappings 返回某 MA 的全部映射(JSON {mappings:[],cdn_endpoints:[]})。
func (t *TCSRegistry) QueryMappings() protogo.Response {
ccCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("mapcount", ccCode)
maps := make([]map[string]interface{}, 0, n)
cdns := []string{}
for i := 1; i <= n; i++ {
if v, _ := sdk.Instance.GetStateByte("mapping", ccCode+":"+strconv.Itoa(i)); len(v) > 0 {
var m map[string]interface{}
_ = json.Unmarshal(v, &m)
maps = append(maps, m)
if ep, ok := m["cdn_endpoint"].(string); ok && ep != "" {
cdns = append(cdns, ep)
}
}
}
b, _ := json.Marshal(map[string]interface{}{"ma_code": ccCode, "mappings": maps, "cdn_endpoints": cdns})
return sdk.Success(b)
}
// ListContents 按状态返回内容数组(空状态返回全部)。
func (t *TCSRegistry) ListContents() protogo.Response {
status := string(sdk.Instance.GetArgs()["status"])
var all []string
if v, _ := sdk.Instance.GetStateByte("allcccodes", ""); len(v) > 0 {
_ = json.Unmarshal(v, &all)
}
out := make([]map[string]interface{}, 0, len(all))
for _, ma := range all {
v, _ := sdk.Instance.GetStateByte("content", ma)
if len(v) == 0 {
continue
}
var c map[string]interface{}
_ = json.Unmarshal(v, &c)
if status == "" || c["status"] == status {
out = append(out, c)
}
}
b, _ := json.Marshal(out)
return sdk.Success(b)
}
func main() {
if err := sandbox.Start(new(TCSRegistry)); err != nil {
_ = errors.New(err.Error())
}
}