Files
MAcode/tcs-iptv/internal/chain/chainmaker.go
T

359 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.
//go:build chainmaker
// Package chain — 真实链后端(长安链 ChainMaker)适配器骨架。
//
// 仅在 `go build -tags chainmaker` 时编译;默认构建由 chainmaker_stub.go 提供占位,
// 因此主工程在没有 ChainMaker Go SDK 依赖时也始终可编译。
//
// 接入步骤(需真实环境):
// 1. 引入 SDK 依赖:
// go get chainmaker.org/chainmaker/sdk-go/v2
// 2. 准备 sdk_config.yml(节点地址、TLS、四角色组织证书),路径由 TCS_CHAINMAKER_SDK_CONF 指定。
// 3. 部署 contracts/tcs_registry 合约,合约名见 contractName 常量。
// 4. 启动:TCS_CHAIN_BACKEND=chainmaker go run -tags chainmaker ./cmd/api-svc
//
// 设计:每个业务角色(监管/审核/CP/运营商)使用各自组织证书的 ChainClient
// 合约内 senderOrg() 据此做链上权限判定(IssueMA/Revoke 仅监管组织)。
// 写操作走 InvokeContract(同步等待上链确认),读操作走 QueryContract(不产生交易)。
package chain
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"chainmaker.org/chainmaker/pb-go/v2/common"
sdk "chainmaker.org/chainmaker/sdk-go/v2"
"github.com/tcs-iptv/tcs/internal/model"
)
const contractName = "tcs_registry"
// ChainMakerClient 是 chain.Client 的真实链实现。
type ChainMakerClient struct {
// clients 为四角色各自证书初始化的链客户端(组织/用户证书不同)。
clients map[Role]*sdk.ChainClient
// mirror 可选 PG 镜像(链为权威,镜像供高效查询)。为 nil 时仅用链。
mirror *sql.DB
}
var _ Client = (*ChainMakerClient)(nil)
// NewChainMakerClient 按 sdk_config.yml 初始化四角色链客户端。
//
// 真实实现需为每个角色加载其组织/用户证书(可在 sdk_config.yml 用多 user 段,
// 或为每个角色单独一个 config 文件)。此处给出装配骨架,证书细节随部署而定。
func NewChainMakerClient(sdkConfPath string, mirror *sql.DB) (Client, error) {
roles := []Role{RoleRegulator, RoleReviewer, RoleCP, RoleOperator}
clients := make(map[Role]*sdk.ChainClient, len(roles))
for _, r := range roles {
// TODO(deploy): 为每个角色加载其证书。示例:约定每角色一个配置文件
// conf := fmt.Sprintf("%s.%s.yml", strings.TrimSuffix(sdkConfPath, ".yml"), r)
cli, err := sdk.NewChainClient(sdk.WithConfPath(sdkConfPath))
if err != nil {
return nil, fmt.Errorf("chainmaker: 初始化角色 %s 客户端失败: %w", r, err)
}
clients[r] = cli
}
return &ChainMakerClient{clients: clients, mirror: mirror}, nil
}
// kv 构造合约入参键值对。
func kv(m map[string][]byte) []*common.KeyValuePair {
out := make([]*common.KeyValuePair, 0, len(m))
for k, v := range m {
out = append(out, &common.KeyValuePair{Key: k, Value: v})
}
return out
}
// invoke 以指定角色身份提交合约写交易(同步等待上链)。
func (c *ChainMakerClient) invoke(role Role, method string, args map[string][]byte) (*common.TxResponse, error) {
cli, ok := c.clients[role]
if !ok {
return nil, fmt.Errorf("chainmaker: 未配置角色 %s 的链客户端", role)
}
// withSyncResult=true:等待交易上链并返回合约执行结果
resp, err := cli.InvokeContract(contractName, method, "", kv(args), -1, true)
if err != nil {
return nil, err
}
if resp.Code != common.TxStatusCode_SUCCESS {
return nil, fmt.Errorf("chainmaker: tx 失败: %s", resp.Message)
}
if resp.ContractResult != nil && resp.ContractResult.Code != 0 {
return nil, mapContractError(string(resp.ContractResult.Message))
}
return resp, nil
}
// query 以指定角色身份发起合约查询(不产生交易)。
func (c *ChainMakerClient) query(role Role, method string, args map[string][]byte) ([]byte, error) {
cli := c.clients[role]
resp, err := cli.QueryContract(contractName, method, "", kv(args), -1)
if err != nil {
return nil, err
}
if resp.ContractResult != nil && resp.ContractResult.Code != 0 {
return nil, mapContractError(string(resp.ContractResult.Message))
}
if resp.ContractResult == nil {
return nil, ErrNotFound
}
return resp.ContractResult.Result, nil
}
// mapContractError 把合约返回的错误消息映射回 chain 包标准错误,保证与 MemoryChain 行为一致。
func mapContractError(msg string) error {
switch {
case strings.Contains(msg, "permission denied"):
return ErrPermissionDenied
case strings.Contains(msg, "already issued"):
return ErrMAAlreadyIssued
case strings.Contains(msg, "hash already exists"):
return ErrHashExists
case strings.Contains(msg, "not issued"):
return ErrMANotIssued
case strings.Contains(msg, "not found"):
return ErrNotFound
default:
return fmt.Errorf("chainmaker: %s", msg)
}
}
// ---- chain.Client 实现(写操作)----
func (c *ChainMakerClient) IssueMA(role Role, req IssueRequest) (string, error) {
contentJSON, _ := json.Marshal(req.Content)
epJSON, _ := json.Marshal(req.Episodes)
resp, err := c.invoke(role, "IssueMA", map[string][]byte{
"ma_code": []byte(req.CCCode),
"ctid": []byte(req.ContentTwinID),
"merkle_root": []byte(req.MerkleRoot),
"file_hash": []byte(req.FileHash),
"perceptual_hash": []byte(req.PerceptualHash),
"episodes": epJSON,
"content": contentJSON,
})
if err != nil {
return "", err
}
// TODO(mirror): 成功后写 PG 镜像(可复用 PersistentChain 的 persist* 逻辑)
return resp.TxId, nil
}
func (c *ChainMakerClient) RegisterHashBinding(role Role, b model.HashBinding) (string, error) {
bj, _ := json.Marshal(b)
resp, err := c.invoke(role, "RegisterHashBinding", map[string][]byte{
"ctid": []byte(b.ContentTwinID), "binding": bj,
})
if err != nil {
return "", err
}
return resp.TxId, nil
}
func (c *ChainMakerClient) RegisterMapping(role Role, m model.Mapping) (string, error) {
mj, _ := json.Marshal(m)
resp, err := c.invoke(role, "RegisterMapping", map[string][]byte{
"ctid": []byte(m.ContentTwinID), "mapping": mj,
})
if err != nil {
return "", err
}
return resp.TxId, nil
}
func (c *ChainMakerClient) RecordVersionChange(vc model.VersionChange) (string, error) {
vj, _ := json.Marshal(vc)
resp, err := c.invoke(RoleReviewer, "RecordVersionChange", map[string][]byte{
"ctid": []byte(vc.ContentTwinID), "vc": vj,
})
if err != nil {
return "", err
}
return resp.TxId, nil
}
func (c *ChainMakerClient) Revoke(role Role, ccCode, reason string) (MappingsResult, error) {
if _, err := c.invoke(role, "Revoke", map[string][]byte{
"ma_code": []byte(ccCode), "reason": []byte(reason),
}); err != nil {
return MappingsResult{}, err
}
return c.QueryMappings(ccCode)
}
func (c *ChainMakerClient) RevokeEpisode(role Role, ccCode string, episode int, reason string) error {
_, err := c.invoke(role, "RevokeEpisode", map[string][]byte{
"ma_code": []byte(ccCode), "episode": []byte(fmt.Sprint(episode)), "reason": []byte(reason),
})
return err
}
func (c *ChainMakerClient) Restore(role Role, ccCode string) error {
_, err := c.invoke(role, "Restore", map[string][]byte{"ma_code": []byte(ccCode)})
return err
}
func (c *ChainMakerClient) RestoreEpisode(role Role, ccCode string, episode int) error {
_, err := c.invoke(role, "RestoreEpisode", map[string][]byte{
"ma_code": []byte(ccCode), "episode": []byte(fmt.Sprint(episode)),
})
return err
}
func (c *ChainMakerClient) SetContentStatus(ccCode, status string) error {
_, err := c.invoke(RoleReviewer, "SetContentStatus", map[string][]byte{
"ma_code": []byte(ccCode), "status": []byte(status),
})
return err
}
// ---- chain.Client 实现(读操作)----
func (c *ChainMakerClient) VerifyHash(ccCode, fileHash string) (VerifyResult, error) {
res, err := c.query(RoleOperator, "VerifyHash", map[string][]byte{
"ma_code": []byte(ccCode), "file_hash": []byte(fileHash),
})
if err != nil {
return VerifyResult{CCCode: ccCode, SubmittedHash: fileHash}, err
}
match := string(res) == "true"
return VerifyResult{Valid: true, CCCode: ccCode, SubmittedHash: fileHash, Match: match}, nil
}
func (c *ChainMakerClient) VerifyEpisodeHash(ccCode string, episode int, fileHash string) (VerifyResult, error) {
res, err := c.query(RoleOperator, "VerifyEpisodeHash", map[string][]byte{
"ma_code": []byte(ccCode), "episode": []byte(fmt.Sprint(episode)), "file_hash": []byte(fileHash),
})
if err != nil {
return VerifyResult{CCCode: ccCode, SubmittedHash: fileHash}, err
}
return VerifyResult{Valid: true, CCCode: ccCode, SubmittedHash: fileHash, Match: string(res) == "true"}, nil
}
func (c *ChainMakerClient) ListEpisodes(ccCode string) ([]model.HashBinding, error) {
res, err := c.query(RoleRegulator, "ListEpisodes", map[string][]byte{"ma_code": []byte(ccCode)})
if err != nil {
return nil, err
}
var out []model.HashBinding
if err := json.Unmarshal(res, &out); err != nil {
return nil, err
}
return out, nil
}
func (c *ChainMakerClient) HashExists(fileHash string) (string, bool) {
res, err := c.query(RoleRegulator, "HashExists", map[string][]byte{"file_hash": []byte(fileHash)})
if err != nil || len(res) == 0 {
return "", false
}
return string(res), true
}
func (c *ChainMakerClient) QueryContent(ccCode string) (model.Content, error) {
res, err := c.query(RoleRegulator, "QueryContent", map[string][]byte{"ma_code": []byte(ccCode)})
if err != nil {
return model.Content{}, err
}
var content model.Content
if err := json.Unmarshal(res, &content); err != nil {
return model.Content{}, err
}
return content, nil
}
func (c *ChainMakerClient) ListContents(status string) ([]model.Content, error) {
// 优先走 PG 镜像(链上范围扫描代价高);无镜像时回源合约范围查询。
res, err := c.query(RoleRegulator, "ListContents", map[string][]byte{"status": []byte(status)})
if err != nil {
return nil, err
}
var out []model.Content
if err := json.Unmarshal(res, &out); err != nil {
return nil, err
}
return out, nil
}
func (c *ChainMakerClient) QueryMappings(ccCode string) (MappingsResult, error) {
res, err := c.query(RoleRegulator, "QueryMappings", map[string][]byte{"ma_code": []byte(ccCode)})
if err != nil {
return MappingsResult{}, err
}
var out MappingsResult
if err := json.Unmarshal(res, &out); err != nil {
return MappingsResult{}, err
}
out.CCCode = ccCode
return out, nil
}
// ---- 多维度查询与 MA 合并/拆分 ----
func (c *ChainMakerClient) QueryByHash(fileHash string) (model.ContentQueryResult, error) {
res, err := c.query(RoleRegulator, "QueryByHash", map[string][]byte{"file_hash": []byte(fileHash)})
if err != nil {
return model.ContentQueryResult{Found: false}, err
}
var out model.ContentQueryResult
if err := json.Unmarshal(res, &out); err != nil {
return model.ContentQueryResult{Found: false}, err
}
return out, nil
}
func (c *ChainMakerClient) QueryByProvincialCode(provincialCode string) (model.ContentQueryResult, error) {
res, err := c.query(RoleRegulator, "QueryByProvincialCode", map[string][]byte{"provincial_code": []byte(provincialCode)})
if err != nil {
return model.ContentQueryResult{Found: false}, err
}
var out model.ContentQueryResult
if err := json.Unmarshal(res, &out); err != nil {
return model.ContentQueryResult{Found: false}, err
}
return out, nil
}
func (c *ChainMakerClient) QueryByLibraryFileID(libraryFileID string) (model.ContentQueryResult, error) {
res, err := c.query(RoleRegulator, "QueryByLibraryFileID", map[string][]byte{"library_file_id": []byte(libraryFileID)})
if err != nil {
return model.ContentQueryResult{Found: false}, err
}
var out model.ContentQueryResult
if err := json.Unmarshal(res, &out); err != nil {
return model.ContentQueryResult{Found: false}, err
}
return out, nil
}
func (c *ChainMakerClient) MergeMA(role Role, req model.MergeRequest) (model.MergeResult, error) {
reqJSON, _ := json.Marshal(req)
resp, err := c.invoke(role, "MergeMA", map[string][]byte{"request": reqJSON})
if err != nil {
return model.MergeResult{}, err
}
var out model.MergeResult
if err := json.Unmarshal(resp.ContractResult.Result, &out); err != nil {
return model.MergeResult{}, err
}
return out, nil
}
func (c *ChainMakerClient) SplitMA(role Role, req model.SplitRequest) (model.SplitResult, error) {
reqJSON, _ := json.Marshal(req)
resp, err := c.invoke(role, "SplitMA", map[string][]byte{"request": reqJSON})
if err != nil {
return model.SplitResult{}, err
}
var out model.SplitResult
if err := json.Unmarshal(resp.ContractResult.Result, &out); err != nil {
return model.SplitResult{}, err
}
return out, nil
}