feat(cmd): add governance cmd
This commit is contained in:
parent
2902f16647
commit
0765965e0c
|
@ -22,7 +22,7 @@ func Start(config *repo.Config) error {
|
|||
|
||||
mux := runtime.NewServeMux(
|
||||
runtime.WithMarshalerOption(runtime.MIMEWildcard,
|
||||
&runtime.JSONPb{OrigName: true, EmitDefaults: true},
|
||||
&runtime.JSONPb{OrigName: true, EmitDefaults: true, EnumsAsInts: true},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ var clientCMD = cli.Command{
|
|||
txCMD(),
|
||||
validatorsCMD(),
|
||||
delVPNodeCMD(),
|
||||
governanceCMD(),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,412 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Rican7/retry"
|
||||
"github.com/Rican7/retry/strategy"
|
||||
"github.com/cheynewallace/tabby"
|
||||
"github.com/fatih/color"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
appchainMgr "github.com/meshplus/bitxhub-core/appchain-mgr"
|
||||
"github.com/meshplus/bitxhub-model/constant"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/meshplus/bitxhub/internal/executor/contracts"
|
||||
"github.com/meshplus/bitxhub/internal/repo"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func governanceCMD() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "governance",
|
||||
Usage: "governance command",
|
||||
Subcommands: cli.Commands{
|
||||
cli.Command{
|
||||
Name: "vote",
|
||||
Usage: "vote to a proposal",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "proposal id",
|
||||
Required: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "info",
|
||||
Usage: "voting information, approve or reject",
|
||||
Required: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "reason",
|
||||
Usage: "reason to vote",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: vote,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "proposals",
|
||||
Usage: "query proposals based on the condition",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "proposal id",
|
||||
Required: false,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "type",
|
||||
Usage: "proposal type, currently only AppchainMgr is supported",
|
||||
Required: false,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "status",
|
||||
Usage: "proposal status, one of proposed, approve or reject",
|
||||
Required: false,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "from",
|
||||
Usage: "the address of the account to which the proposal was made",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Action: getProposals,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "chain",
|
||||
Usage: "query chain status by chain id",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "chain id",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: getChainStatusById,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func vote(ctx *cli.Context) error {
|
||||
id := ctx.String("id")
|
||||
info := ctx.String("info")
|
||||
reason := ctx.String("reason")
|
||||
|
||||
if info != "approve" && info != "reject" {
|
||||
return fmt.Errorf("the info parameter can only have a value of \"approve\" or \"reject\"")
|
||||
}
|
||||
|
||||
repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyPath := repo.GetKeyPath(repoRoot)
|
||||
|
||||
resp, err := sendTx(ctx, constant.GovernanceContractAddr.String(), 0, uint64(pb.TransactionData_INVOKE), keyPath, uint64(pb.TransactionData_BVM), "Vote",
|
||||
pb.String(id), pb.String(info), pb.String(reason))
|
||||
if err != nil {
|
||||
return fmt.Errorf("send transaction error: %s", err.Error())
|
||||
}
|
||||
hash := gjson.Get(string(resp), "tx_hash").String()
|
||||
|
||||
var data []byte
|
||||
if err = retry.Retry(func(attempt uint) error {
|
||||
data, err = getTxReceipt(ctx, hash)
|
||||
if err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
}
|
||||
if errInfo, ok := m["error"]; ok {
|
||||
fmt.Println("get transaction receipt error: " + errInfo.(string) + "... retry later")
|
||||
return fmt.Errorf(errInfo.(string))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}, strategy.Wait(500*time.Millisecond),
|
||||
); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error())
|
||||
}
|
||||
|
||||
m := &runtime.JSONPb{OrigName: true, EmitDefaults: true, EnumsAsInts: true}
|
||||
receipt := &pb.Receipt{}
|
||||
if err = m.Unmarshal(data, receipt); err != nil {
|
||||
return fmt.Errorf("jsonpb unmarshal receipt error: %w", err)
|
||||
}
|
||||
|
||||
if receipt.IsSuccess() {
|
||||
color.Green("vote successfully!\n")
|
||||
} else {
|
||||
color.Red("vote error: %s\n", string(receipt.Ret))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProposals(ctx *cli.Context) error {
|
||||
id := ctx.String("id")
|
||||
typ := ctx.String("type")
|
||||
status := ctx.String("status")
|
||||
from := ctx.String("from")
|
||||
|
||||
if err := checkArgs(id, typ, status, from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyPath := repo.GetKeyPath(repoRoot)
|
||||
|
||||
proposals := make([]contracts.Proposal, 0)
|
||||
if id == "" {
|
||||
if typ != "" {
|
||||
proposals, err = getProposalsByConditions(ctx, keyPath, "GetProposalsByTyp", typ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get proposals by type error: %w", err)
|
||||
}
|
||||
if len(proposals) == 0 {
|
||||
status = ""
|
||||
from = ""
|
||||
}
|
||||
}
|
||||
if status != "" {
|
||||
proposalsTmp, err := getProposalsByConditions(ctx, keyPath, "GetProposalsByStatus", status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get proposals by status error: %w", err)
|
||||
}
|
||||
proposals = getdDuplicateProposals(proposals, proposalsTmp)
|
||||
if len(proposals) == 0 {
|
||||
from = ""
|
||||
}
|
||||
}
|
||||
if from != "" {
|
||||
proposalsTmp, err := getProposalsByConditions(ctx, keyPath, "GetProposalsByFrom", from)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get proposals by from error: %w", err)
|
||||
}
|
||||
proposals = getdDuplicateProposals(proposals, proposalsTmp)
|
||||
}
|
||||
} else {
|
||||
proposals, err = getProposalsByConditions(ctx, keyPath, "GetProposal", id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get proposals by id error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
printProposal(proposals)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkArgs(id, typ, status, from string) error {
|
||||
if id == "" &&
|
||||
typ == "" &&
|
||||
status == "" &&
|
||||
from == "" {
|
||||
return fmt.Errorf("input at least one query condition")
|
||||
}
|
||||
if typ != "" &&
|
||||
typ != string(contracts.AppchainMgr) &&
|
||||
typ != string(contracts.RuleMgr) &&
|
||||
typ != string(contracts.NodeMgr) &&
|
||||
typ != string(contracts.ServiceMgr) {
|
||||
return fmt.Errorf("illegal proposal type")
|
||||
}
|
||||
if status != "" &&
|
||||
status != string(contracts.PROPOSED) &&
|
||||
status != string(contracts.APPOVED) &&
|
||||
status != string(contracts.REJECTED) {
|
||||
return fmt.Errorf("illegal proposal status")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getdDuplicateProposals(ps1, ps2 []contracts.Proposal) []contracts.Proposal {
|
||||
if len(ps1) == 0 {
|
||||
return ps2
|
||||
}
|
||||
proposals := make([]contracts.Proposal, 0)
|
||||
for _, p1 := range ps1 {
|
||||
for _, p2 := range ps2 {
|
||||
if p1.Id == p2.Id {
|
||||
proposals = append(proposals, p1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return proposals
|
||||
}
|
||||
|
||||
func getProposalsByConditions(ctx *cli.Context, keyPath string, menthod string, arg string) ([]contracts.Proposal, error) {
|
||||
resp, err := sendTx(ctx, constant.GovernanceContractAddr.String(), 0, uint64(pb.TransactionData_INVOKE), keyPath, uint64(pb.TransactionData_BVM), menthod,
|
||||
pb.String(arg))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send transaction error: %w", err)
|
||||
}
|
||||
hash := gjson.Get(string(resp), "tx_hash").String()
|
||||
|
||||
var data []byte
|
||||
if err = retry.Retry(func(attempt uint) error {
|
||||
data, err = getTxReceipt(ctx, hash)
|
||||
if err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
}
|
||||
if errInfo, ok := m["error"]; ok {
|
||||
fmt.Println("get transaction receipt error: " + errInfo.(string) + "... retry later")
|
||||
return fmt.Errorf(errInfo.(string))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}, strategy.Wait(500*time.Millisecond),
|
||||
); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error())
|
||||
}
|
||||
|
||||
m := &runtime.JSONPb{OrigName: true, EmitDefaults: true, EnumsAsInts: true}
|
||||
receipt := &pb.Receipt{}
|
||||
if err = m.Unmarshal(data, receipt); err != nil {
|
||||
return nil, fmt.Errorf("jsonpb unmarshal receipt error: %w", err)
|
||||
}
|
||||
|
||||
if receipt.IsSuccess() {
|
||||
proposals := make([]contracts.Proposal, 0)
|
||||
if menthod == "GetProposal" {
|
||||
proposal := contracts.Proposal{}
|
||||
err = json.Unmarshal(receipt.Ret, &proposal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal receipt error: %w", err)
|
||||
}
|
||||
proposals = append(proposals, proposal)
|
||||
} else {
|
||||
err = json.Unmarshal(receipt.Ret, &proposals)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal receipt error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return proposals, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf(string(receipt.Ret))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func printProposal(proposals []contracts.Proposal) {
|
||||
var table [][]string
|
||||
table = append(table, []string{"Id", "Type", "Status", "ApproveNum", "RejectNum", "ElectorateNum", "ThresholdNum", "Des"})
|
||||
|
||||
for _, pro := range proposals {
|
||||
table = append(table, []string{
|
||||
pro.Id,
|
||||
string(pro.Typ),
|
||||
string(pro.Status),
|
||||
strconv.Itoa(int(pro.ApproveNum)),
|
||||
strconv.Itoa(int(pro.AgainstNum)),
|
||||
strconv.Itoa(int(pro.ElectorateNum)),
|
||||
strconv.Itoa(int(pro.ThresholdNum)),
|
||||
pro.Des,
|
||||
})
|
||||
}
|
||||
|
||||
PrintTable(table, true)
|
||||
}
|
||||
|
||||
func PrintTable(rows [][]string, header bool) {
|
||||
// Print the table
|
||||
t := tabby.New()
|
||||
if header {
|
||||
addRow(t, rows[0], header)
|
||||
rows = rows[1:]
|
||||
}
|
||||
for _, row := range rows {
|
||||
addRow(t, row, false)
|
||||
}
|
||||
t.Print()
|
||||
}
|
||||
|
||||
func addRow(t *tabby.Tabby, rawLine []string, header bool) {
|
||||
// Convert []string to []interface{}
|
||||
row := make([]interface{}, len(rawLine))
|
||||
for i, v := range rawLine {
|
||||
row[i] = v
|
||||
}
|
||||
|
||||
// Add line to the table
|
||||
if header {
|
||||
t.AddHeader(row...)
|
||||
} else {
|
||||
t.AddLine(row...)
|
||||
}
|
||||
}
|
||||
|
||||
func getChainStatusById(ctx *cli.Context) error {
|
||||
id := ctx.String("id")
|
||||
|
||||
repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyPath := repo.GetKeyPath(repoRoot)
|
||||
|
||||
resp, err := sendTx(ctx, constant.AppchainMgrContractAddr.String(), 0, uint64(pb.TransactionData_INVOKE), keyPath, uint64(pb.TransactionData_BVM), "GetAppchain",
|
||||
pb.String(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("send transaction error: %s", err.Error())
|
||||
}
|
||||
|
||||
hash := gjson.Get(string(resp), "tx_hash").String()
|
||||
|
||||
var data []byte
|
||||
if err = retry.Retry(func(attempt uint) error {
|
||||
data, err = getTxReceipt(ctx, hash)
|
||||
if err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error() + "... retry later")
|
||||
return err
|
||||
}
|
||||
if errInfo, ok := m["error"]; ok {
|
||||
fmt.Println("get transaction receipt error: " + errInfo.(string) + "... retry later")
|
||||
return fmt.Errorf(errInfo.(string))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}, strategy.Wait(500*time.Millisecond),
|
||||
); err != nil {
|
||||
fmt.Println("get transaction receipt error: " + err.Error())
|
||||
}
|
||||
|
||||
m := &runtime.JSONPb{OrigName: true, EmitDefaults: true, EnumsAsInts: true}
|
||||
receipt := &pb.Receipt{}
|
||||
if err = m.Unmarshal(data, receipt); err != nil {
|
||||
return fmt.Errorf("jsonpb unmarshal receipt error: %w", err)
|
||||
}
|
||||
|
||||
if receipt.IsSuccess() {
|
||||
chain := &appchainMgr.Appchain{}
|
||||
if err := json.Unmarshal(receipt.Ret, chain); err != nil {
|
||||
return fmt.Errorf("unmarshal receipt error: %w", err)
|
||||
}
|
||||
color.Green("appchain %s is %s", chain.ID, string(chain.Status))
|
||||
} else {
|
||||
color.Red("get chain status error: %s\n", string(receipt.Ret))
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -19,12 +19,7 @@ func getReceipt(ctx *cli.Context) error {
|
|||
return fmt.Errorf("please input transaction hash")
|
||||
}
|
||||
|
||||
url, err := getURL(ctx, "receipt/"+ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := httpGet(ctx, url)
|
||||
data, err := getTxReceipt(ctx, ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -33,3 +28,17 @@ func getReceipt(ctx *cli.Context) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTxReceipt(ctx *cli.Context, hash string) ([]byte, error) {
|
||||
url, err := getURL(ctx, "receipt/"+hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := httpGet(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
@ -87,45 +87,67 @@ func sendTransaction(ctx *cli.Context) error {
|
|||
keyPath = repo.GetKeyPath(repoRoot)
|
||||
}
|
||||
|
||||
resp, err := sendTx(ctx, toString, amount, txType, keyPath, 0, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("send transaction: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(resp))
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendTx(ctx *cli.Context, toString string, amount uint64, txType uint64, keyPath string, vmType uint64, method string, args ...*pb.Arg) ([]byte, error) {
|
||||
|
||||
key, err := repo.LoadKey(keyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong key: %w", err)
|
||||
return nil, fmt.Errorf("wrong key: %w", err)
|
||||
}
|
||||
|
||||
from, err := key.PrivKey.PublicKey().Address()
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong private key: %w", err)
|
||||
return nil, fmt.Errorf("wrong private key: %w", err)
|
||||
}
|
||||
|
||||
to := types.NewAddressByStr(toString)
|
||||
|
||||
invokePayload := &pb.InvokePayload{
|
||||
Method: method,
|
||||
Args: args,
|
||||
}
|
||||
invokePayloadData, err := invokePayload.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := &pb.TransactionData{
|
||||
Type: pb.TransactionData_Type(txType),
|
||||
Amount: amount,
|
||||
Type: pb.TransactionData_Type(txType),
|
||||
Amount: amount,
|
||||
VmType: pb.TransactionData_VMType(vmType),
|
||||
Payload: invokePayloadData,
|
||||
}
|
||||
payload, err := data.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getNonceUrl, err := getURL(ctx, fmt.Sprintf("pendingNonce/%s", from.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encodedNonce, err := httpGet(ctx, getNonceUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err := parseResponse(encodedNonce)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := strconv.ParseUint(ret, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse pending nonce :%w", err)
|
||||
return nil, fmt.Errorf("parse pending nonce :%w", err)
|
||||
}
|
||||
|
||||
tx := &pb.Transaction{
|
||||
|
@ -137,25 +159,23 @@ func sendTransaction(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
if err := tx.Sign(key.PrivKey); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqData, err := json.Marshal(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url, err := getURL(ctx, "transaction")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := httpPost(ctx, url, reqData)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println(string(resp))
|
||||
|
||||
return nil
|
||||
return resp, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue