8db9d33694
- A.1 备案对接: BindFiling/QueryFiling 关联网标号+备案号 - A.2 监管上报: DailyRegulatoryReport 日报 - B.1 号段管理: ListSegments + /admin/segments - C.1/C.2 全国统计按省聚合 + 跨省协同(单一可信源天然联动) - F.2 全国监管大屏: NationalStats(按省/类目/状态) - B(遗留) 监管大屏BFF: internal/bff + cmd/console-bff, 密钥仅存后端浏览器只用会话令牌 - G 真实链合约源码: contracts/tcs_registry/registry.go (ChainMaker Go) - 新增9个API+BFF服务; 5项新测试; 端到端BFF验证 - D/E(压测/等保/HSM)/F.1(标准)/真实链部署 标注需外部环境
141 lines
4.6 KiB
Go
141 lines
4.6 KiB
Go
// Package main 是 TCS-IPTV 可信数据空间的 ChainMaker 智能合约(Go,三期 A.2)。
|
|
//
|
|
// 本合约为独立合约模块(独立 go.mod),按 ChainMaker docker-go/wasm 合约规范部署。
|
|
// 与 internal/chain.Client 接口语义一一对应;MVP/二期用 MemoryChain 等价实现,
|
|
// 具备链环境后部署本合约,由 chain-svc 通过 ChainMaker Go SDK 调用替换 MemoryChain。
|
|
//
|
|
// 状态键设计(KV):
|
|
//
|
|
// content:{maCode} -> Content JSON
|
|
// binding:{maCode}:{idx} -> HashBinding JSON
|
|
// hashidx:{fileHash} -> maCode(防换壳重发)
|
|
// mapping:{maCode}:{idx} -> Mapping JSON
|
|
// ctid2ma:{ctid} -> maCode
|
|
//
|
|
// 权限:通过 sender 组织/角色证书判断(仅监管组织可 IssueMA/Revoke)。
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"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" // 监管组织(仅其可签发/下架)
|
|
)
|
|
|
|
// 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"))
|
|
}
|
|
|
|
// senderOrg 取调用方组织标识(基于证书 OU/OrgId)。
|
|
func senderOrg() string {
|
|
org, _ := sdk.Instance.GetSenderOrgId()
|
|
return org
|
|
}
|
|
|
|
// 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()
|
|
maCode := string(args["ma_code"])
|
|
ctid := string(args["ctid"])
|
|
fileHash := string(args["file_hash"])
|
|
contentJSON := args["content"]
|
|
|
|
// MA 不可重复签发
|
|
if existing, _ := sdk.Instance.GetStateByte("content", maCode); len(existing) > 0 {
|
|
return sdk.Error("MA already issued (1:1 binding immutable)")
|
|
}
|
|
// 防换壳重发:同哈希不可绑定到不同 MA
|
|
if bound, _ := sdk.Instance.GetStateByte("hashidx", fileHash); len(bound) > 0 {
|
|
return sdk.Error("content hash already exists")
|
|
}
|
|
|
|
_ = sdk.Instance.PutStateByte("content", maCode, contentJSON)
|
|
binding := map[string]string{"hash_type": "file_sha256", "hash_value": fileHash, "version": "v1.0"}
|
|
bj, _ := json.Marshal(binding)
|
|
_ = sdk.Instance.PutStateByte("binding", maCode+":0", bj)
|
|
_ = sdk.Instance.PutStateByte("hashidx", fileHash, []byte(maCode))
|
|
_ = sdk.Instance.PutStateByte("ctid2ma", ctid, []byte(maCode))
|
|
|
|
sdk.Instance.EmitEvent("RegisterSuccess", []string{maCode, fileHash})
|
|
return sdk.Success([]byte(maCode))
|
|
}
|
|
|
|
// RegisterMapping 注册三方编码映射(MA 必须已签发)。
|
|
func (t *TCSRegistry) RegisterMapping() protogo.Response {
|
|
args := sdk.Instance.GetArgs()
|
|
maCode := string(args["ma_code"])
|
|
if v, _ := sdk.Instance.GetStateByte("content", maCode); len(v) == 0 {
|
|
return sdk.Error("MA not issued")
|
|
}
|
|
idx := string(args["idx"])
|
|
_ = sdk.Instance.PutStateByte("mapping", maCode+":"+idx, args["mapping"])
|
|
return sdk.Success([]byte("ok"))
|
|
}
|
|
|
|
// VerifyHash 校验提交哈希是否与绑定哈希一致。
|
|
func (t *TCSRegistry) VerifyHash() protogo.Response {
|
|
args := sdk.Instance.GetArgs()
|
|
maCode := string(args["ma_code"])
|
|
fileHash := string(args["file_hash"])
|
|
bound, _ := sdk.Instance.GetStateByte("hashidx", fileHash)
|
|
if string(bound) == maCode {
|
|
return sdk.Success([]byte("true"))
|
|
}
|
|
return sdk.Success([]byte("false"))
|
|
}
|
|
|
|
// Revoke 下架(仅监管组织)。
|
|
func (t *TCSRegistry) Revoke() protogo.Response {
|
|
if senderOrg() != orgRegulator {
|
|
return sdk.Error("permission denied: only regulator can revoke")
|
|
}
|
|
args := sdk.Instance.GetArgs()
|
|
maCode := string(args["ma_code"])
|
|
cj, _ := sdk.Instance.GetStateByte("content", maCode)
|
|
if len(cj) == 0 {
|
|
return sdk.Error("not found")
|
|
}
|
|
var content map[string]interface{}
|
|
_ = json.Unmarshal(cj, &content)
|
|
content["status"] = "revoked"
|
|
nj, _ := json.Marshal(content)
|
|
_ = sdk.Instance.PutStateByte("content", maCode, nj)
|
|
sdk.Instance.EmitEvent("Revoked", []string{maCode})
|
|
return sdk.Success([]byte("ok"))
|
|
}
|
|
|
|
// QueryContent 查询内容主记录。
|
|
func (t *TCSRegistry) QueryContent() protogo.Response {
|
|
maCode := string(sdk.Instance.GetArgs()["ma_code"])
|
|
v, _ := sdk.Instance.GetStateByte("content", maCode)
|
|
if len(v) == 0 {
|
|
return sdk.Error("not found")
|
|
}
|
|
return sdk.Success(v)
|
|
}
|
|
|
|
func main() {
|
|
err := sandbox.Start(new(TCSRegistry))
|
|
if err != nil {
|
|
_ = errors.New(err.Error())
|
|
}
|
|
}
|