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, priv *sm2.PrivateKey, pub *sm2.PublicKey, opt HttpCOptions, ) (*Client, error) { 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 path 请求路径 // @Param http 请求方法(GET | POST) // @Param result 接收请求返回结果的结构体指针 // @Param body 请求体(尽在method为POST时使用) // @Param priv 请求私钥 // @Param pub 请求公钥 func (c *Client) RequestWithSignature( path string, method string, result any, body map[string]interface{}, priv *sm2.PrivateKey, pub *sm2.PublicKey, ) (response HttpResponse[any], err error) { response = HttpResponse[any]{ Status: 0, } var pubHex string if priv != nil { var err error 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 } err = json.Unmarshal(bodyBytes, result) // 如果bodyBytes解析失败直接将信息保存到ErrData if err != nil { response.ErrData = string(bodyBytes) return response, nil } 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 } func genHttpResponse[D any](data D, response *HttpResponse[any], err error) (*HttpResponse[D], error) { return &HttpResponse[D]{ Data: data, Status: response.Status, ErrData: response.ErrData, }, err } // Ping https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id15 func (c *Client) Ping() (*HttpResponse[PingResponse], error) { return retry(func() (*HttpResponse[PingResponse], error) { result := PingResponse{} resp, err := c.RequestWithSignature( "/SCManager?action=ping", "GET", &result, nil, nil, nil, ) return genHttpResponse[PingResponse](result, &resp, err) }) } // StartContract TODO 未跑通 // StartContract 启动合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id60 func (c *Client) StartContract(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) { result := PingResponse{} resp, err := c.RequestWithSignature( path, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // StartContractByYPK TODO 待测试 // StartContractByYPK 启动合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id13 func (c *Client) StartContractByYPK(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ExecuteContract 调用合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id69 func (c *Client) ExecuteContract( contractID string, operation string, arg string, withDynamicAnalysis bool, withSignature bool, ) (*HttpResponse[ExecuteContractResponse[any]], error) { body := map[string]any{ "action": "executeContract", "contractID": contractID, "operation": operation, "withDynamicAnalysis": withDynamicAnalysis, "arg": arg, } if withSignature { body["signature"] = c.Sign(contractID+"|"+operation+"|"+arg+"|"+c.pubHex, nil, nil) body["pubkey"] = c.pubHex } return retry(func() (*HttpResponse[ExecuteContractResponse[any]], error) { result := ExecuteContractResponse[any]{} resp, err := c.RequestWithSignature( "/SCManager", "POST", &result, body, nil, nil, ) return genHttpResponse[ExecuteContractResponse[any]](result, &resp, err) }) } // KillContractProcess TODO 待测试 // KillContractProcess 停止合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id122 func (c *Client) KillContractProcess(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // KillAllContract TODO 待测试 // KillAllContract 停止所有合约 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id131 func (c *Client) KillAllContract() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"killAllContract"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ApplyNodeRole TODO 待测试 // ApplyNodeRole 申请角色 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html func (c *Client) ApplyNodeRole(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // AuthNodeRole TODO 待测试 // AuthNodeRole 授权角色 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html func (c *Client) AuthNodeRole( 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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, priv, pub, ) return genHttpResponse[any](result, &resp, err) }) } // DistributeContract TODO 待测试, 用sse获取问题未解决!!! // 分发合约项目 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html //func (c *Client) 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{} // resp, err := c.RequestWithSignature( // httpPath, // "GET", // &result, // nil, // nil, // nil, // ) // // return genHttpResponse[any](result, &resp, err) // }) //} // SaveFile TODO 待测试 func (c *Client) SaveFile( 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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ListProjectPermission TODO 待测试 func (c *Client) ListProjectPermission( 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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // StartContractMultiPoint TODO 待测试 func (c *Client) StartContractMultiPoint( 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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // LoadNodeConfig TODO 待测试 // 获取节点配置信息 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id497 func (c *Client) LoadNodeConfig() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"loadNodeConfig"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // UpdateConfig TODO 待测试 // 修改节点配置 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id504 // @Param key {licence,projectDir,yjsPath,dataChain,doipConfig,nodeCenter,nodeName,masterAddress,resetNodeCenterWS} func (c *Client) UpdateConfig(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ResetNodeManager TODO 待测试 // 设置pubkey为node manager func (c *Client) ResetNodeManager() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"resetNodeManager"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // LockEdit TODO 待测试 // 锁定某个用户的的私有目录编辑功能 func (c *Client) LockEdit() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"lockEdit"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // UnlockEdit TODO 待测试 // 解锁某个用户的的私有目录编辑功能 func (c *Client) UnlockEdit() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"unlockEdit"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // AddNode TODO 待测试 func (c *Client) AddNode(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ApplyRole TODO 待测试 // 申请角色 func (c *Client) ApplyRole(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // AuthNodeManager TODO 待测试 func (c *Client) AuthNodeManager(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ListAllUsers TODO 待测试 func (c *Client) ListAllUsers() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listAllUsers"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ListNodes TODO 待测试 func (c *Client) ListNodes() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listNodes"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // CreateTrustUnit TODO 待测试 // 建立可信执行集群 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id664 func (c *Client) CreateTrustUnit( 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) { result := PingResponse{} resp, err := c.RequestWithSignature( "/SCManager", "POST", &result, body, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ListTrustUnits TODO 待测试 // 查看可信执行集群列表 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id657 func (c *Client) ListTrustUnits() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listTrustUnits"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ListContractProcess TODO 待测试 // 查询合约进程 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id444 func (c *Client) ListContractProcess() (*HttpResponse[any], error) { params := url.Values{ "action": []string{"listContractProcess"}, } httpPath := fmt.Sprintf("/SCManager?%s", params.Encode()) return retry(func() (*HttpResponse[any], error) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // DownloadContract TODO 待测试 // 下载合约项目 // https://public.internetapi.cn/docs/bdcontract/doc/ContractAPI.html#id25 func (c *Client) DownloadContract(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) { result := PingResponse{} resp, err := c.RequestWithSignature( httpPath, "GET", &result, nil, nil, nil, ) return genHttpResponse[any](result, &resp, err) }) } // ConfigNode TODO 待测试 // 配置合约引擎节点,若节点没有设置过node manager,将当前key设置为node manager // struct arg { // nodeName string // dataChain string // masterAddress string // nodeCenter string // // 配置中心节点时使用 // LHSProxyAddress string // } func (c *Client) ConfigNode(arg map[string]string) bool { res, err := c.ResetNodeManager() if err != nil || res.Status == 0 { return false } eg := errgroup.Group{} for key, value := range arg { eg.Go(func() error { res, err = c.UpdateConfig(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 = c.ApplyNodeRole(value) if err != nil { return err } if res.Status == 0 { return fmt.Errorf(res.ErrData) } return nil }) } eg.Go(func() error { res, err = c.AuthNodeRole(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 = c.LoadNodeConfig() 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 } }