951 lines
20 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.

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.yandata.net/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 {licenceprojectDiryjsPathdataChaindoipConfignodeCenternodeNamemasterAddressresetNodeCenterWS}
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
}
}