package client import ( "bytes" "crypto" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "strconv" "strings" "time" "github.com/tjfoc/gmsm/sm2" "golang.org/x/sync/errgroup" "go.fusiongalaxy.cn/bdware/bdcontract-client/sm2util" ) type HttpCOptions struct { MaxRetries int } type Client struct { baseUrl string priv *sm2.PrivateKey pub *sm2.PublicKey pubHex string httpc *http.Client maxRetry int } func NewClient( baseUrl string, privStr string, pubStr string, opt HttpCOptions, ) (*Client, error) { pub, err := sm2util.ParsePublicKey(pubStr) if err != nil { return nil, err } priv, err := sm2util.ParsePrivateKey(privStr, pub) if err != nil { return nil, err } pubHex, err := sm2util.CheckSm2KeyPair(priv, pub) if err != nil { return nil, err } if opt.MaxRetries == 0 { opt.MaxRetries = 3 } return &Client{ baseUrl: baseUrl, pub: pub, pubHex: pubHex, priv: priv, httpc: &http.Client{Timeout: 10 * time.Second}, maxRetry: opt.MaxRetries, }, nil } // RequestWithSignature // @Description 发送带有签名的请求 // @Param c 客户端 // @Param path 请求路径 // @Param http 请求方法(GET | POST) // @Param result 接收请求返回结果的结构体指针 // @Param body 请求体(尽在method为POST时使用) // @Param priv 请求私钥 // @Param pub 请求公钥 func RequestWithSignature[T any]( c *Client, path string, method string, body map[string]interface{}, priv *sm2.PrivateKey, pub *sm2.PublicKey, ) (response *HttpResponse[T], err error) { response = &HttpResponse[T]{ Status: 0, } var pubHex string if priv != nil { pubHex, err = sm2util.CheckSm2KeyPair(priv, pub) if err != nil { return response, err } } else { priv = c.priv pubHex = c.pubHex } rawUrl := c.baseUrl + path u := fmt.Sprintf("%s%spubKey=%s", rawUrl, func() string { if strings.Contains(path, "?") { return "&" } return "?" }(), pubHex, ) var resp *http.Response switch strings.ToUpper(method) { case "POST": if body == nil { body = make(map[string]interface{}) } body["sign"] = c.Sign(u[strings.Index(u, "?")+1:], priv, nil) bodyJson, err := json.Marshal(body) if err != nil { return response, err } resp, err = c.httpc.Post(rawUrl, "application/json", bytes.NewBuffer(bodyJson)) if err != nil { return response, err } case "GET": params := url.Values{ "sign": {c.Sign(u[strings.Index(u, "?")+1:], priv, nil)}, "pubKey": {pubHex}, } if strings.Contains(rawUrl, "?") { rawUrl = rawUrl + "&" + params.Encode() } else { rawUrl = rawUrl + "?" + params.Encode() } resp, err = c.httpc.Get(rawUrl) if err != nil { return response, err } default: return response, fmt.Errorf("unsupported method: %s", method) } defer resp.Body.Close() // 读取所有数据直到 EOF bodyBytes, err := io.ReadAll(resp.Body) // 如果bodyBytes不能被json解析直接将信息保存到ErrData if err != nil || !json.Valid(bodyBytes) { response.ErrData = string(bodyBytes) return response, nil } var result T err = json.Unmarshal(bodyBytes, &result) // 如果bodyBytes解析失败直接将信息保存到ErrData if err != nil { response.ErrData = string(bodyBytes) return response, nil } response.Data = result response.Status = resp.StatusCode return response, nil } func (c *Client) Sign(data string, priv *sm2.PrivateKey, signer crypto.SignerOpts) string { if priv == nil { priv = c.priv } sig, err := priv.Sign(rand.Reader, []byte(data), signer) if err != nil { log.Fatal(err) } return hex.EncodeToString(sig) } func genUrlParamsFromObject(obj map[string]interface{}) string { params := make([]string, 0) for key, value := range obj { params = append(params, fmt.Sprintf("%s=%v", key, value)) } return strings.Join(params, "&") } func retry[T any](fn func() (T, error)) (T, error) { var lastErr error for attempt := 0; attempt < 3; attempt++ { resp, err := fn() if err == nil { return resp, nil } lastErr = err time.Sleep(100 * time.Millisecond * time.Duration(attempt+1)) } var zero T return zero, lastErr } // Ping https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id15 func Ping(c *Client) (*HttpResponse[PingResponse], error) { return retry(func() (*HttpResponse[PingResponse], error) { return RequestWithSignature[PingResponse]( c, "/SCManager?action=ping", "GET", nil, nil, nil, ) }) } // StartContract TODO 未跑通 // StartContract 启动合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id60 func StartContract(c *Client, code string) (*HttpResponse[any], error) { params := url.Values{ "action": {"startContract"}, "script": {code}, } path := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, path, "GET", nil, nil, nil, ) }) } // StartContractByYPK TODO 待测试 // StartContractByYPK 启动合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id13 func StartContractByYPK(c *Client, isPrivate bool, path string, script string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"startContractByYPK"}, "script": []string{script}, "isPrivate": []string{strconv.FormatBool(isPrivate)}, "path": []string{path}, "owner": []string{c.pubHex}, "aim": []string{"onStartContract"}, "signature": []string{c.Sign("Fixed|"+path+"|"+c.pubHex, nil, nil)}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ExecuteContract 调用合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id69 func ExecuteContract[T any]( c *Client, contractID string, operation string, arg any, withDynamicAnalysis bool, withSignature bool, ) (*HttpResponse[ExecuteContractResponse[T]], error) { body := map[string]any{ "action": "executeContract", "contractID": contractID, "operation": operation, "withDynamicAnalysis": withDynamicAnalysis, "arg": arg, } if withSignature { argStr := "" if v, ok := arg.(string); ok { argStr = v } else { v, err := json.Marshal(arg) if err != nil { return nil, fmt.Errorf("arg %v marshal err", arg) } argStr = string(v) } body["signature"] = c.Sign(contractID+"|"+operation+"|"+argStr+"|"+c.pubHex, nil, nil) body["pubkey"] = c.pubHex } return retry(func() (*HttpResponse[ExecuteContractResponse[T]], error) { return RequestWithSignature[ExecuteContractResponse[T]]( c, "/SCManager", "POST", body, nil, nil, ) }) } // KillContractProcess TODO 待测试 // KillContractProcess 停止合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id122 func KillContractProcess(c *Client, contractID string, requestID string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"killContractProcess"}, "id": []string{contractID}, } if requestID != "" { params["requestID"] = []string{requestID} } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // KillAllContract TODO 待测试 // KillAllContract 停止所有合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id131 func KillAllContract(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"killAllContract"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ApplyNodeRole TODO 待测试 // ApplyNodeRole 申请角色 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html func ApplyNodeRole(c *Client, role string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"applyNodeRole"}, "role": []string{role}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // AuthNodeRole TODO 待测试 // AuthNodeRole 授权角色 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html func AuthNodeRole( c *Client, isAccept bool, authorizedPubKey string, priv *sm2.PrivateKey, pub *sm2.PublicKey, ) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"authNodeRole"}, "isAccept": []string{strconv.FormatBool(isAccept)}, "authorizedPubKey": []string{authorizedPubKey}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, priv, pub, ) }) } // DistributeContract TODO 待测试, 用sse获取问题未解决!!! // 分发合约项目 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html //func DistributeContract( // nodeIDs string, // projectName string, // isPrivate bool, //) (*HttpResponse[any], error) { // params := url.Values{ // "action": []string{"distributeContract"}, // "isPrivate": []string{strconv.FormatBool(isPrivate)}, // "nodeIDs": []string{nodeIDs}, // "projectName": []string{projectName}, // } // // httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) // // return retry(func() (*HttpResponse[any], error) { // result := PingResponse{} // return RequestWithSignature( // httpPath, // "GET", // &result, // nil, // nil, // nil, // ) // // return genHttpResponse[any](result, &resp, err) // }) //} // SaveFile TODO 待测试 func SaveFile( c *Client, content string, isAppend bool, isPrivate bool, path string, ) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"saveFile"}, "isAppend": []string{strconv.FormatBool(isAppend)}, "isPrivate": []string{strconv.FormatBool(isPrivate)}, "content": []string{content}, "path": []string{path}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ListProjectPermission TODO 待测试 func ListProjectPermission( c *Client, isPrivate bool, path string, ) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listProjectPermission"}, "isPrivate": []string{strconv.FormatBool(isPrivate)}, "path": []string{path}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // StartContractMultiPoint TODO 待测试 func StartContractMultiPoint( c *Client, peersID string, contractType int, selectUnitNum int, projectName string, isPrivate bool, sponsorPeerID string, ) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"startContractMultiPoint"}, "peersID": []string{peersID}, "projectName": []string{projectName}, "sponsorPeerID": []string{sponsorPeerID}, "isPrivate": []string{strconv.FormatBool(isPrivate)}, "contractType": []string{strconv.Itoa(contractType)}, "selectUnitNum": []string{strconv.Itoa(selectUnitNum)}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // LoadNodeConfig TODO 待测试 // 获取节点配置信息 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id497 func LoadNodeConfig(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"loadNodeConfig"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // UpdateConfig TODO 待测试 // 修改节点配置 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id504 // @Param key {licence,projectDir,yjsPath,dataChain,doipConfig,nodeCenter,nodeName,masterAddress,resetNodeCenterWS} func UpdateConfig(c *Client, key string, val string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"updateConfig"}, "key": []string{key}, "val": []string{val}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ResetNodeManager TODO 待测试 // 设置pubkey为node manager func ResetNodeManager(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"resetNodeManager"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // LockEdit TODO 待测试 // 锁定某个用户的的私有目录编辑功能 func LockEdit(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"lockEdit"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // UnlockEdit TODO 待测试 // 解锁某个用户的的私有目录编辑功能 func UnlockEdit(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"unlockEdit"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // AddNode TODO 待测试 func AddNode(c *Client, nodePubKey string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"addNode"}, "nodePubKey": []string{nodePubKey}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ApplyRole TODO 待测试 // 申请角色 func ApplyRole(c *Client, role string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"applyRole"}, "role": []string{role}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // AuthNodeManager TODO 待测试 func AuthNodeManager(c *Client, isAccept bool, authorizedPubKey string) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"authNodeManager"}, "isAccept": []string{strconv.FormatBool(isAccept)}, "authorizedPubKey": []string{authorizedPubKey}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ListAllUsers TODO 待测试 func ListAllUsers(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listAllUsers"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ListNodes TODO 待测试 func ListNodes(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listNodes"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // CreateTrustUnit TODO 待测试 // 建立可信执行集群 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id664 func CreateTrustUnit( c *Client, data []struct { nodeName string pubkey string }, msg string, ) (*HttpResponse[any], error) { body := map[string]any{ "action": "createTrustUnit", "data": data, "msg": msg, "pubKey": c.pubHex, "sign": c.Sign("action=createTrustUnit&data="+fmt.Sprintf("%v", data)+"&msg="+msg+"&pubKey="+c.pubHex, nil, nil), } return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, "/SCManager", "POST", body, nil, nil, ) }) } // ListTrustUnits TODO 待测试 // 查看可信执行集群列表 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id657 func ListTrustUnits(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listTrustUnits"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ListContractProcess TODO 待测试 // 查询合约进程 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id444 func ListContractProcess(c *Client) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listContractProcess"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // DownloadContract TODO 待测试 // 下载合约项目 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id25 func DownloadContract(c *Client, projectName string, isPrivate bool, timestamp int) (*HttpResponse[any], error) { params := url.Values{ "action": []string{"downloadContract"}, "projectName": []string{projectName}, "isPrivate": []string{strconv.FormatBool(isPrivate)}, "timestamp": []string{strconv.Itoa(timestamp)}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { return RequestWithSignature[any]( c, httpPath, "GET", nil, nil, nil, ) }) } // ConfigNode TODO 待测试 // 配置合约引擎节点,若节点没有设置过node manager,将当前key设置为node manager // struct arg { // nodeName string // dataChain string // masterAddress string // nodeCenter string // // 配置中心节点时使用 // LHSProxyAddress string // } func ConfigNode(c *Client, arg map[string]string) bool { res, err := ResetNodeManager(c) if err != nil || res.Status == 0 { return false } eg := errgroup.Group{} for key, value := range arg { eg.Go(func() error { res, err = UpdateConfig(c, key, value) if err != nil { return err } if res.Status == 0 { return fmt.Errorf(res.ErrData) } return nil }) } for _, value := range []string{"ContractProvider", "ContractUser", "ContractInstanceManager"} { eg.Go(func() error { res, err = ApplyNodeRole(c, value) if err != nil { return err } if res.Status == 0 { return fmt.Errorf(res.ErrData) } return nil }) } eg.Go(func() error { res, err = AuthNodeRole(c, true, c.pubHex, nil, nil) if err != nil { return err } if res.Status == 0 { return fmt.Errorf(res.ErrData) } return nil }) err = eg.Wait() if err != nil { return false } res, err = LoadNodeConfig(c) if err != nil || res.Status == 0 { return false } for k, v := range arg { if arg[remapNodeConfigKey(k)] != v { return false } } return true } func remapNodeConfigKey(k string) string { switch k { case "dataChain": return "bdledger" default: return k } }