bdcontract-client/client/client.go
2024-11-29 17:57:52 +08:00

1001 lines
22 KiB
Go
Raw Permalink 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 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 {licenceprojectDiryjsPathdataChaindoipConfignodeCenternodeNamemasterAddressresetNodeCenterWS}
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
}
}