Unverified Commit 8c4040cc authored by vipwzw's avatar vipwzw Committed by GitHub

Merge pull request #932 from bysomeone/add-dapp-vote

Add dapp vote
parents 48909645 03270ff2
......@@ -48,7 +48,7 @@ for y in $objects; do
# find the objects location in the repository tree
other=$(echo "${allObjects}" | grep "$sha")
#lineBreak=`echo -e "\n"`
output="${output}\n${size},${compressedSize},${other}"
output="${output}\\n${size},${compressedSize},${other}"
done
echo -e "$output" | column -t -s ', '
......
This diff is collapsed.
......@@ -30,6 +30,7 @@ import (
_ "github.com/33cn/plugin/plugin/dapp/trade" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/unfreeze" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/valnode" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/vote" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/wasm" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/x2ethereum" //auto gen
)
all:
bash build.sh $(OUT) $(FLAG)
#!/bin/bash
# 官方ci集成脚本
strpwd=$(pwd)
strcmd=${strpwd##*dapp/}
strapp=${strcmd%/cmd*}
OUT_DIR="${1}/$strapp"
#FLAG=$2
mkdir -p "${OUT_DIR}"
cp ./build/* "${OUT_DIR}"
/*Package commands implement dapp client commands*/
package commands
import (
jsonrpc "github.com/33cn/chain33/rpc/jsonclient"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/spf13/cobra"
)
/*
* 实现合约对应客户端
*/
// Cmd vote client command
func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "vote",
Short: "vote command",
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(
//create tx
createGroupCMD(),
updateGroupCMD(),
createVoteCMD(),
commitVoteCMD(),
closeVoteCMD(),
updateMemberCMD(),
//query rpc
groupInfoCMD(),
voteInfoCMD(),
memberInfoCMD(),
listGroupCMD(),
listVoteCMD(),
listMemberCMD(),
)
return cmd
}
func markRequired(cmd *cobra.Command, params ...string) {
for _, param := range params {
_ = cmd.MarkFlagRequired(param)
}
}
func sendCreateTxRPC(cmd *cobra.Command, actionName string, req types.Message) {
title, _ := cmd.Flags().GetString("title")
cfg := types.GetCliSysParam(title)
rpcAddr, _ := cmd.Flags().GetString("rpc_laddr")
payLoad := types.MustPBToJSON(req)
pm := &rpctypes.CreateTxIn{
Execer: cfg.ExecName(vty.VoteX),
ActionName: actionName,
Payload: payLoad,
}
var res string
ctx := jsonrpc.NewRPCCtx(rpcAddr, "Chain33.CreateTransaction", pm, &res)
ctx.RunWithoutMarshal()
}
func sendQueryRPC(cmd *cobra.Command, funcName string, req, reply types.Message) {
title, _ := cmd.Flags().GetString("title")
cfg := types.GetCliSysParam(title)
rpcAddr, _ := cmd.Flags().GetString("rpc_laddr")
payLoad := types.MustPBToJSON(req)
query := &rpctypes.Query4Jrpc{
Execer: cfg.ExecName(vty.VoteX),
FuncName: funcName,
Payload: payLoad,
}
ctx := jsonrpc.NewRPCCtx(rpcAddr, "Chain33.Query", query, reply)
ctx.Run()
}
package commands
import (
"fmt"
"os"
"time"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/spf13/cobra"
)
func createGroupCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "createGroup",
Aliases: []string{"cg"},
Short: "create tx(create vote group)",
Run: createGroup,
Example: "createGroup -n=group1 -a=admin1 -m=member1 -m=member2",
}
createGroupFlags(cmd)
return cmd
}
func createGroupFlags(cmd *cobra.Command) {
cmd.Flags().StringP("name", "n", "", "group name")
cmd.Flags().StringArrayP("admins", "a", nil, "group admin address array, default set creator as admin")
cmd.Flags().StringArrayP("members", "m", nil, "group member address array")
cmd.Flags().UintSliceP("weights", "w", nil, "member vote weight array")
cmd.Flags().StringArrayP("nicks", "c", nil, "group member nick name array")
markRequired(cmd, "name")
}
func formatAddMembers(addrs, nickNames []string, voteWeights []uint) []*vty.GroupMember {
if len(voteWeights) == 0 {
voteWeights = make([]uint, len(addrs))
}
if len(voteWeights) != len(voteWeights) {
fmt.Fprintf(os.Stderr, "member address array length should equal with vote weight array length")
return nil
}
if len(nickNames) == 0 {
nickNames = make([]string, len(addrs))
}
if len(nickNames) != len(addrs) {
fmt.Fprintf(os.Stderr, "member nick name array, add member addr array should have same length")
return nil
}
members := make([]*vty.GroupMember, 0)
for i, addr := range addrs {
members = append(members, &vty.GroupMember{Addr: addr, VoteWeight: uint32(voteWeights[i]), NickName: nickNames[i]})
}
return members
}
func createGroup(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
admins, _ := cmd.Flags().GetStringArray("admins")
memberAddrs, _ := cmd.Flags().GetStringArray("members")
weights, _ := cmd.Flags().GetUintSlice("weights")
nicks, _ := cmd.Flags().GetStringArray("nicks")
if name == "" {
fmt.Fprintf(os.Stderr, "ErrNilGroupName")
return
}
members := formatAddMembers(memberAddrs, nicks, weights)
if members == nil {
return
}
params := &vty.CreateGroup{
Name: name,
Admins: admins,
Members: members,
}
sendCreateTxRPC(cmd, vty.NameCreateGroupAction, params)
}
func updateGroupCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "updateGroup",
Aliases: []string{"ug"},
Short: "create tx(update group members or admin)",
Run: updateGroup,
Example: "updateGroup -g=id -a=addMember1 -a=addMember2 -r=removeMember1 ...",
}
updateGroupFlags(cmd)
return cmd
}
func updateGroupFlags(cmd *cobra.Command) {
cmd.Flags().StringP("groupID", "g", "", "group id")
cmd.Flags().StringArrayP("addMembers", "m", nil, "group member address array for adding")
cmd.Flags().UintSliceP("weights", "w", nil, "member vote weight array for adding")
cmd.Flags().StringArrayP("nicks", "c", nil, "group member nick name array")
cmd.Flags().StringArrayP("removeMembers", "v", nil, "group member address array for removing")
cmd.Flags().StringArrayP("addAdmins", "a", nil, "group admin address array for adding")
cmd.Flags().StringArrayP("removeAdmins", "r", nil, "group admin address array for removing")
markRequired(cmd, "groupID")
}
func updateGroup(cmd *cobra.Command, args []string) {
groupID, _ := cmd.Flags().GetString("groupID")
addAddrs, _ := cmd.Flags().GetStringArray("addMembers")
weights, _ := cmd.Flags().GetUintSlice("weights")
nicks, _ := cmd.Flags().GetStringArray("nicks")
removeAddrs, _ := cmd.Flags().GetStringArray("removeMembers")
addAdmins, _ := cmd.Flags().GetStringArray("addAdmins")
removeAdmins, _ := cmd.Flags().GetStringArray("removeAdmins")
if groupID == "" {
fmt.Fprintf(os.Stderr, "ErrNilGroupID")
return
}
addMembers := formatAddMembers(addAddrs, nicks, weights)
if addMembers == nil {
return
}
params := &vty.UpdateGroup{
GroupID: groupID,
RemoveMembers: removeAddrs,
AddMembers: addMembers,
AddAdmins: addAdmins,
RemoveAdmins: removeAdmins,
}
sendCreateTxRPC(cmd, vty.NameUpdateGroupAction, params)
}
func createVoteCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "createVote",
Aliases: []string{"ctv"},
Short: "create tx(create vote)",
Run: createVote,
}
createVoteFlags(cmd)
return cmd
}
func createVoteFlags(cmd *cobra.Command) {
cmd.Flags().StringP("name", "n", "", "vote name")
cmd.Flags().StringP("groupID", "g", "", "belonging group id")
cmd.Flags().StringArrayP("options", "o", nil, "vote option array")
cmd.Flags().Int64P("beginTime", "b", 0, "vote begin unix timestamp, default set current time")
cmd.Flags().StringP("duration", "d", "24h", "vote duration time, such as(10s, 10m, 10h), default 24h")
markRequired(cmd, "name", "groupID", "options")
}
func createVote(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
groupID, _ := cmd.Flags().GetString("groupID")
options, _ := cmd.Flags().GetStringArray("options")
beginTime, _ := cmd.Flags().GetInt64("beginTime")
duration, _ := cmd.Flags().GetString("duration")
if name == "" {
fmt.Fprintf(os.Stderr, "ErrNilVoteName")
return
}
if len(groupID) == 0 {
fmt.Fprintf(os.Stderr, "ErrNilGroupID")
return
}
if len(options) == 0 {
fmt.Fprintf(os.Stderr, "ErrNilOptions")
return
}
if beginTime == 0 {
beginTime = types.Now().Unix()
}
dt, err := time.ParseDuration(duration)
if err != nil {
fmt.Fprintf(os.Stderr, "InvalidDurationTime:"+err.Error())
return
}
endTime := beginTime + int64(dt/time.Second)
params := &vty.CreateVote{
Name: name,
GroupID: groupID,
VoteOptions: options,
BeginTimestamp: beginTime,
EndTimestamp: endTime,
}
sendCreateTxRPC(cmd, vty.NameCreateVoteAction, params)
}
func commitVoteCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "commitVote",
Aliases: []string{"cmv"},
Short: "create tx(commit vote)",
Run: commitVote,
}
commitVoteFlags(cmd)
return cmd
}
func commitVoteFlags(cmd *cobra.Command) {
cmd.Flags().StringP("voteID", "v", "", "vote id")
cmd.Flags().Uint32P("optionIndex", "o", 0, "voting option index in option array")
markRequired(cmd, "voteID", "optionIndex")
}
func commitVote(cmd *cobra.Command, args []string) {
voteID, _ := cmd.Flags().GetString("voteID")
optionIndex, _ := cmd.Flags().GetUint32("optionIndex")
if voteID == "" {
fmt.Fprintf(os.Stderr, "ErrNilVoteID")
return
}
params := &vty.CommitVote{
VoteID: voteID,
OptionIndex: optionIndex,
}
sendCreateTxRPC(cmd, vty.NameCommitVoteAction, params)
}
func closeVoteCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "closeVote",
Aliases: []string{"csv"},
Short: "create tx(close vote)",
Run: closeVote,
}
closeVoteFlags(cmd)
return cmd
}
func closeVoteFlags(cmd *cobra.Command) {
cmd.Flags().StringP("voteID", "v", "", "vote id")
markRequired(cmd, "voteID")
}
func closeVote(cmd *cobra.Command, args []string) {
voteID, _ := cmd.Flags().GetString("voteID")
if voteID == "" {
fmt.Fprintf(os.Stderr, "ErrNilVoteID")
return
}
params := &vty.CloseVote{
VoteID: voteID,
}
sendCreateTxRPC(cmd, vty.NameCloseVoteAction, params)
}
func updateMemberCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "updateMember",
Aliases: []string{"um"},
Short: "create tx(update member name)",
Run: updateMember,
}
updateMemberFlags(cmd)
return cmd
}
func updateMemberFlags(cmd *cobra.Command) {
cmd.Flags().StringP("name", "n", "", "member name")
markRequired(cmd, "name")
}
func updateMember(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
if name == "" {
fmt.Fprintf(os.Stderr, "ErrNilMemberName")
return
}
params := &vty.UpdateMember{
Name: name,
}
sendCreateTxRPC(cmd, vty.NameUpdateMemberAction, params)
}
package commands
import (
"fmt"
"os"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/spf13/cobra"
)
func groupInfoCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "groupInfo",
Aliases: []string{"gf"},
Short: "get group infos",
Run: groupInfo,
Example: "groupInfo -g=id1 -g=id2...",
}
groupInfoFlags(cmd)
return cmd
}
func groupInfoFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayP("groupIDs", "g", nil, "group id array")
markRequired(cmd, "groupIDs")
}
func groupInfo(cmd *cobra.Command, args []string) {
groupIDs, _ := cmd.Flags().GetStringArray("groupIDs")
if len(groupIDs) == 0 {
fmt.Fprintf(os.Stderr, "ErrNilGroupIDs")
return
}
params := &vty.ReqStrings{
Items: groupIDs,
}
info := &vty.GroupInfos{}
sendQueryRPC(cmd, "GetGroups", params, info)
}
func voteInfoCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "voteInfo",
Aliases: []string{"vf"},
Short: "get vote info",
Run: voteInfo,
}
voteInfoFlags(cmd)
return cmd
}
func voteInfoFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayP("voteIDs", "v", nil, "vote id array")
markRequired(cmd, "voteID")
}
func voteInfo(cmd *cobra.Command, args []string) {
voteIDs, _ := cmd.Flags().GetStringArray("voteIDs")
if len(voteIDs) == 0 {
fmt.Fprintf(os.Stderr, "ErrNilVoteID")
return
}
params := &vty.ReqStrings{
Items: voteIDs,
}
info := &vty.ReplyVoteList{}
sendQueryRPC(cmd, "GetVotes", params, info)
}
func memberInfoCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "memberInfo",
Aliases: []string{"mf"},
Short: "get member info",
Run: memberInfo,
}
memberInfoFlags(cmd)
return cmd
}
func memberInfoFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayP("addrs", "a", nil, "member address array")
markRequired(cmd, "addr")
}
func memberInfo(cmd *cobra.Command, args []string) {
addrs, _ := cmd.Flags().GetStringArray("addrs")
if len(addrs) == 0 {
fmt.Fprintf(os.Stderr, "ErrNilAddress")
return
}
params := &vty.ReqStrings{
Items: addrs,
}
info := &vty.MemberInfos{}
sendQueryRPC(cmd, "GetMembers", params, info)
}
func listGroupCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "listGroup",
Aliases: []string{"lg"},
Short: "show group list",
Run: listGroup,
}
listCmdFlags(cmd)
return cmd
}
func listGroup(cmd *cobra.Command, args []string) {
runListCMD(cmd, "ListGroup", &vty.GroupInfos{})
}
func listVoteCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "listVote",
Aliases: []string{"lv"},
Short: "show vote list",
Run: listVote,
}
listVoteFlags(cmd)
return cmd
}
func listVoteFlags(cmd *cobra.Command) {
cmd.Flags().StringP("groupID", "g", "", "list vote belongs to specified group, list all if not set")
cmd.Flags().Uint32P("status", "t", 0, "vote status")
listCmdFlags(cmd)
}
func listVote(cmd *cobra.Command, args []string) {
groupID, _ := cmd.Flags().GetString("groupID")
status, _ := cmd.Flags().GetUint32("status")
listReq := getListReq(cmd)
req := &vty.ReqListVote{
GroupID: groupID,
ListReq: listReq,
Status: status,
}
sendQueryRPC(cmd, "ListVote", req, &vty.ReplyVoteList{})
}
func listMemberCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "listMember",
Aliases: []string{"lm"},
Short: "show member list",
Run: listMember,
}
listCmdFlags(cmd)
return cmd
}
func listMember(cmd *cobra.Command, args []string) {
runListCMD(cmd, "ListMember", &vty.MemberInfos{})
}
func listCmdFlags(cmd *cobra.Command) {
cmd.Flags().StringP("startItem", "s", "", "list start item id, default nil value")
cmd.Flags().Uint32P("count", "c", 5, "list count, default 5")
cmd.Flags().Uint32P("direction", "d", 1, "list direction, default 1 (Ascending order)")
}
func runListCMD(cmd *cobra.Command, funcName string, reply types.Message) {
req := getListReq(cmd)
sendQueryRPC(cmd, funcName, req, reply)
}
func getListReq(cmd *cobra.Command) *vty.ReqListItem {
startID, _ := cmd.Flags().GetString("startItem")
count, _ := cmd.Flags().GetUint32("count")
direction, _ := cmd.Flags().GetUint32("direction")
req := &vty.ReqListItem{
StartItemID: startID,
Count: int32(count),
Direction: int32(direction),
}
return req
}
package executor
import (
"encoding/hex"
"github.com/33cn/chain33/system/dapp"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
type action struct {
db dbm.KV
txHash []byte
fromAddr string
blockTime int64
height int64
index int
}
func newAction(v *vote, tx *types.Transaction, index int) *action {
return &action{db: v.GetStateDB(),
txHash: tx.Hash(),
fromAddr: tx.From(),
blockTime: v.GetBlockTime(),
height: v.GetHeight(),
index: index}
}
func (a *action) getGroupInfo(groupID string) (*vty.GroupInfo, error) {
info := &vty.GroupInfo{}
err := readStateDB(a.db, formatStateIDKey(groupID), info)
if err == types.ErrNotFound {
err = errGroupNotExist
}
return info, err
}
func (a *action) getVoteInfo(voteID string) (*vty.VoteInfo, error) {
info := &vty.VoteInfo{}
err := readStateDB(a.db, formatStateIDKey(voteID), info)
if err == types.ErrNotFound {
err = errVoteNotExist
}
return info, err
}
func (a *action) createGroup(create *vty.CreateGroup) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
group := &vty.GroupInfo{}
group.Name = create.Name
group.ID = formatGroupID(dapp.HeightIndexStr(a.height, int64(a.index)))
//添加创建者作为默认管理员
group.Admins = append(group.Admins, a.fromAddr)
for _, addr := range create.GetAdmins() {
if addr != a.fromAddr {
group.Admins = append(group.Admins, addr)
}
}
group.Members = create.Members
// set default vote weight
for _, member := range group.Members {
if member.VoteWeight < 1 {
member.VoteWeight = 1
}
}
group.MemberNum = uint32(len(group.Members))
group.Creator = a.fromAddr
group.Description = create.GetDescription()
groupValue := types.Encode(group)
receipt.KV = append(receipt.KV, &types.KeyValue{Key: formatStateIDKey(group.ID), Value: groupValue})
receipt.Logs = append(receipt.Logs, &types.ReceiptLog{Ty: vty.TyCreateGroupLog, Log: groupValue})
return receipt, nil
}
func (a *action) updateGroup(update *vty.UpdateGroup) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
group, err := a.getGroupInfo(update.GroupID)
if err != nil {
elog.Error("vote exec updateGroup", "txHash", a.txHash, "err", err)
return nil, errStateDBGet
}
addrMap := make(map[string]int)
for index, member := range group.Members {
addrMap[member.Addr] = index
}
// remove members
for _, addr := range update.GetRemoveMembers() {
if index, ok := addrMap[addr]; ok {
group.Members = append(group.Members[:index], group.Members[index+1:]...)
delete(addrMap, addr)
}
}
// add members
for _, member := range update.GetAddMembers() {
if _, ok := addrMap[member.Addr]; !ok {
group.Members = append(group.Members, member)
}
}
group.MemberNum = uint32(len(group.Members))
adminMap := make(map[string]int)
for index, addr := range group.Admins {
adminMap[addr] = index
}
// remove admins
for _, addr := range update.GetRemoveAdmins() {
if index, ok := adminMap[addr]; ok {
group.Admins = append(group.Admins[:index], group.Admins[index+1:]...)
delete(adminMap, addr)
}
}
// add admins
for _, addr := range update.GetAddAdmins() {
if _, ok := adminMap[addr]; !ok {
group.Admins = append(group.Admins, addr)
}
}
groupValue := types.Encode(group)
receipt.KV = append(receipt.KV, &types.KeyValue{Key: formatStateIDKey(group.ID), Value: groupValue})
receipt.Logs = append(receipt.Logs, &types.ReceiptLog{Ty: vty.TyUpdateGroupLog, Log: groupValue})
return receipt, nil
}
func (a *action) createVote(create *vty.CreateVote) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
vote := &vty.VoteInfo{}
vote.ID = formatVoteID(dapp.HeightIndexStr(a.height, int64(a.index)))
vote.BeginTimestamp = create.BeginTimestamp
vote.EndTimestamp = create.EndTimestamp
vote.Name = create.Name
vote.GroupID = create.GroupID
vote.Description = create.Description
vote.Creator = a.fromAddr
vote.VoteOptions = make([]*vty.VoteOption, 0)
for _, option := range create.VoteOptions {
vote.VoteOptions = append(vote.VoteOptions, &vty.VoteOption{Option: option})
}
voteValue := types.Encode(vote)
receipt.KV = append(receipt.KV, &types.KeyValue{Key: formatStateIDKey(vote.ID), Value: voteValue})
receipt.Logs = append(receipt.Logs, &types.ReceiptLog{Ty: vty.TyCreateVoteLog, Log: voteValue})
return receipt, nil
}
func (a *action) commitVote(commit *vty.CommitVote) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
vote, err := a.getVoteInfo(commit.VoteID)
if err != nil {
elog.Error("vote exec commitVote", "txHash", a.txHash, "get vote err", err)
return nil, errStateDBGet
}
group, err := a.getGroupInfo(vote.GroupID)
if err != nil {
elog.Error("vote exec commitVote", "txHash", a.txHash, "get group err", err)
return nil, errStateDBGet
}
var voteWeight uint32
for _, member := range group.Members {
if member.Addr == a.fromAddr {
voteWeight = member.VoteWeight
}
}
vote.VoteOptions[commit.OptionIndex].Score += voteWeight
info := &vty.CommitInfo{Addr: a.fromAddr}
vote.CommitInfos = append(vote.CommitInfos, info)
voteValue := types.Encode(vote)
//提交的哈希和权重等信息不记录到statedb中
info.VoteWeight = voteWeight
info.TxHash = hex.EncodeToString(a.txHash)
receipt.KV = append(receipt.KV, &types.KeyValue{Key: formatStateIDKey(vote.ID), Value: voteValue})
receipt.Logs = append(receipt.Logs, &types.ReceiptLog{Ty: vty.TyCommitVoteLog, Log: types.Encode(info)})
return receipt, nil
}
func (a *action) closeVote(close *vty.CloseVote) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
vote, err := a.getVoteInfo(close.VoteID)
if err != nil {
elog.Error("vote exec commitVote", "txHash", a.txHash, "get vote err", err)
return nil, errStateDBGet
}
vote.Status = voteStatusClosed
voteValue := types.Encode(vote)
receipt.KV = append(receipt.KV, &types.KeyValue{Key: formatStateIDKey(vote.ID), Value: voteValue})
receipt.Logs = append(receipt.Logs, &types.ReceiptLog{Ty: vty.TyCloseVoteLog, Log: voteValue})
return receipt, nil
}
func (a *action) updateMember(update *vty.UpdateMember) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk}
return receipt, nil
}
package executor
import (
"encoding/hex"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
// CheckTx 实现自定义检验交易接口,供框架调用
func (v *vote) CheckTx(tx *types.Transaction, index int) error {
// implement code
txHash := hex.EncodeToString(tx.Hash())
var action vty.VoteAction
err := types.Decode(tx.Payload, &action)
if err != nil {
elog.Error("vote CheckTx", "txHash", txHash, "Decode payload error", err)
return types.ErrActionNotSupport
}
if action.Ty == vty.TyCreateGroupAction {
err = v.checkCreateGroup(action.GetCreateGroup())
} else if action.Ty == vty.TyUpdateGroupAction {
err = v.checkUpdateGroup(action.GetUpdateGroup(), tx, index)
} else if action.Ty == vty.TyCreateVoteAction {
err = v.checkCreateVote(action.GetCreateVote(), tx, index)
} else if action.Ty == vty.TyCommitVoteAction {
err = v.checkCommitVote(action.GetCommitVote(), tx, index)
} else if action.Ty == vty.TyCloseVoteAction {
err = v.checkCloseVote(action.GetCloseVote(), tx, index)
} else if action.Ty == vty.TyUpdateMemberAction {
err = v.checkUpdateMember(action.GetUpdateMember())
} else {
err = types.ErrActionNotSupport
}
if err != nil {
elog.Error("vote CheckTx", "txHash", txHash, "actionName", tx.ActionName(), "err", err, "actionData", action)
}
return err
}
func checkMemberValidity(members []*vty.GroupMember) error {
filter := make(map[string]struct{}, len(members))
for _, member := range members {
if member.GetAddr() == "" {
return types.ErrInvalidAddress
}
if _, ok := filter[member.Addr]; ok {
return errDuplicateMember
}
filter[member.Addr] = struct{}{}
}
return nil
}
func (v *vote) checkCreateGroup(create *vty.CreateGroup) error {
if create.GetName() == "" {
return errEmptyName
}
//检测组成员是否有重复
if err := checkMemberValidity(create.GetMembers()); err != nil {
return err
}
//检测管理员是否有重复
if checkSliceItemDuplicate(create.GetAdmins()) {
return errDuplicateAdmin
}
return nil
}
func (v *vote) checkUpdateGroup(update *vty.UpdateGroup, tx *types.Transaction, index int) error {
action := newAction(v, tx, index)
groupInfo, err := action.getGroupInfo(update.GetGroupID())
if err != nil {
return err
}
if !checkSliceItemExist(action.fromAddr, groupInfo.GetAdmins()) {
return errAddrPermissionDenied
}
//防止将管理员全部删除
if len(update.RemoveAdmins) >= len(groupInfo.GetAdmins()) && len(update.AddAdmins) == 0 {
return errAddrPermissionDenied
}
addrs := make([]string, 0, len(update.RemoveMembers)+len(update.AddAdmins)+len(update.RemoveAdmins))
addrs = append(addrs, update.RemoveMembers...)
addrs = append(addrs, update.AddAdmins...)
addrs = append(addrs, update.RemoveAdmins...)
for _, member := range update.AddMembers {
if len(member.Addr) != addrLen {
return types.ErrInvalidAddress
}
}
for _, addr := range addrs {
if len(addr) != addrLen {
elog.Error("checkUpdateGroup", "invalid addr", addr)
return types.ErrInvalidAddress
}
}
//保证管理员地址合法性
for _, addr := range update.GetAddAdmins() {
if err := dapp.CheckAddress(v.GetAPI().GetConfig(), addr, v.GetHeight()); err != nil {
elog.Error("checkUpdateGroup", "addr", addr, "CheckAddress err", err)
return types.ErrInvalidAddress
}
}
return nil
}
func (v *vote) checkCreateVote(create *vty.CreateVote, tx *types.Transaction, index int) error {
if create.GetName() == "" {
return errEmptyName
}
action := newAction(v, tx, index)
groupInfo, err := action.getGroupInfo(create.GetGroupID())
if err != nil {
return err
}
if !checkSliceItemExist(action.fromAddr, groupInfo.GetAdmins()) {
return errAddrPermissionDenied
}
if create.GetEndTimestamp() <= v.GetBlockTime() || create.EndTimestamp <= create.BeginTimestamp {
return errInvalidVoteTime
}
if len(create.VoteOptions) < 2 {
return errInvalidVoteOption
}
return nil
}
func (v *vote) checkCommitVote(commit *vty.CommitVote, tx *types.Transaction, index int) error {
action := newAction(v, tx, index)
voteInfo, err := action.getVoteInfo(commit.GetVoteID())
if err != nil {
return err
}
if voteInfo.BeginTimestamp > action.blockTime {
return errVoteNotStarted
}
if voteInfo.EndTimestamp <= action.blockTime {
return errVoteAlreadyFinished
}
if voteInfo.Status == voteStatusClosed {
return errVoteAlreadyClosed
}
if commit.OptionIndex >= uint32(len(voteInfo.VoteOptions)) {
return errInvalidOptionIndex
}
groupInfo, err := action.getGroupInfo(voteInfo.GroupID)
if err != nil {
return err
}
// check if exist in group members
if !checkMemberExist(action.fromAddr, groupInfo.Members) {
return errAddrPermissionDenied
}
// check if already vote
for _, info := range voteInfo.GetCommitInfos() {
if action.fromAddr == info.Addr {
return errAddrAlreadyVoted
}
}
return nil
}
func (v *vote) checkCloseVote(close *vty.CloseVote, tx *types.Transaction, index int) error {
action := newAction(v, tx, index)
voteInfo, err := action.getVoteInfo(close.GetVoteID())
if err != nil {
return err
}
if voteInfo.Creator != action.fromAddr {
return errAddrPermissionDenied
}
if voteInfo.Status == voteStatusClosed {
return errVoteAlreadyClosed
}
return nil
}
func (v *vote) checkUpdateMember(update *vty.UpdateMember) error {
if update.GetName() == "" {
return errEmptyName
}
return nil
}
package executor
import (
"testing"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
func TestVote_CheckTx_CreateGroup(t *testing.T) {
tcArr := []*testcase{{
index: 1,
payload: &vty.CreateGroup{},
expectCheckErr: errEmptyName,
}, {
index: 2,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{}}},
expectCheckErr: types.ErrInvalidAddress,
}, {
index: 3,
payload: &vty.CreateGroup{
Name: "test",
Members: []*vty.GroupMember{{
Addr: testAddrs[0],
}, {
Addr: testAddrs[0],
}},
},
expectCheckErr: errDuplicateMember,
}, {
index: 4,
payload: &vty.CreateGroup{Name: "test", Admins: []string{testAddrs[0], testAddrs[0]}},
expectCheckErr: errDuplicateAdmin,
}, {
index: 5,
payload: &vty.CreateGroup{Name: "test"},
},
}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
func TestVote_CheckTx_UpdateGroup(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
execType: testTypeExecLocal,
}, {
index: 1,
payload: &vty.UpdateGroup{},
expectCheckErr: errGroupNotExist,
}, {
index: 2,
payload: &vty.UpdateGroup{GroupID: groupID},
priv: privKeys[1],
expectCheckErr: errAddrPermissionDenied,
}, {
index: 3,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: testAddrs[:]},
expectCheckErr: errAddrPermissionDenied,
}, {
index: 4,
payload: &vty.UpdateGroup{GroupID: groupID, AddMembers: []*vty.GroupMember{{Addr: "errAddr"}}},
expectCheckErr: types.ErrInvalidAddress,
}, {
index: 5,
payload: &vty.UpdateGroup{GroupID: groupID, AddAdmins: []string{"errAddr"}},
expectCheckErr: types.ErrInvalidAddress,
}, {
index: 6,
payload: &vty.UpdateGroup{GroupID: groupID, AddAdmins: []string{address.MultiSignAddress(privKeys[0].PubKey().Bytes())}},
expectCheckErr: types.ErrInvalidAddress,
}, {
index: 7,
payload: &vty.UpdateGroup{GroupID: groupID, AddAdmins: []string{testAddrs[1]}},
},
}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
func TestVote_CheckTx_CreateVote(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
execType: testTypeExecLocal,
}, {
index: 1,
payload: &vty.CreateVote{},
expectCheckErr: errEmptyName,
}, {
index: 2,
payload: &vty.CreateVote{Name: "vote"},
expectCheckErr: errGroupNotExist,
}, {
index: 3,
payload: &vty.CreateVote{Name: "vote", GroupID: groupID},
priv: privKeys[1],
expectCheckErr: errAddrPermissionDenied,
}, {
index: 4,
payload: &vty.CreateVote{Name: "vote", GroupID: groupID},
expectCheckErr: errInvalidVoteTime,
}, {
index: 5,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
BeginTimestamp: testBlockTime + 1,
EndTimestamp: testBlockTime + 1,
},
expectCheckErr: errInvalidVoteTime,
}, {
index: 6,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
EndTimestamp: testBlockTime + 1,
},
expectCheckErr: errInvalidVoteOption,
}, {
index: 7,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
EndTimestamp: testBlockTime + 1,
VoteOptions: []string{"A", "B"},
},
},
}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
func TestVote_CheckTx_CommitVote(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
vote2 := formatVoteID(dapp.HeightIndexStr(testHeight, 7))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
execType: testTypeExecLocal,
}, {
index: 1,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
EndTimestamp: testBlockTime + 1,
VoteOptions: []string{"A", "B"},
},
execType: testTypeExecLocal,
}, {
index: 2,
payload: &vty.CommitVote{},
expectCheckErr: errVoteNotExist,
}, {
index: 3,
payload: &vty.CommitVote{VoteID: voteID, OptionIndex: 10},
expectCheckErr: errInvalidOptionIndex,
}, {
index: 4,
payload: &vty.CommitVote{VoteID: voteID},
priv: privKeys[1],
expectCheckErr: errAddrPermissionDenied,
}, {
index: 5,
payload: &vty.CommitVote{VoteID: voteID},
execType: testTypeExecLocal,
}, {
index: 6,
payload: &vty.CommitVote{VoteID: voteID},
expectCheckErr: errAddrAlreadyVoted,
}, {
index: 7,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
BeginTimestamp: testBlockTime + 1,
EndTimestamp: testBlockTime + 2,
VoteOptions: []string{"A", "B"},
},
execType: testTypeExecLocal,
}, {
index: 8,
payload: &vty.CommitVote{VoteID: vote2},
expectCheckErr: errVoteNotStarted,
}}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
func TestVote_CheckTx_CloseVote(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
execType: testTypeExecLocal,
}, {
index: 1,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
EndTimestamp: testBlockTime + 1,
VoteOptions: []string{"A", "B"},
},
execType: testTypeExecLocal,
}, {
index: 2,
payload: &vty.CloseVote{},
expectCheckErr: errVoteNotExist,
}, {
index: 3,
payload: &vty.CloseVote{VoteID: voteID},
priv: privKeys[1],
expectCheckErr: errAddrPermissionDenied,
}, {
index: 4,
payload: &vty.CloseVote{VoteID: voteID},
execType: testTypeExecLocal,
}, {
index: 5,
payload: &vty.CloseVote{VoteID: voteID},
expectCheckErr: errVoteAlreadyClosed,
},
}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
func TestVote_CheckTx_UpdateMember(t *testing.T) {
tcArr := []*testcase{{
index: 0,
payload: &vty.UpdateMember{},
expectCheckErr: errEmptyName,
}, {
index: 1,
payload: &vty.UpdateMember{Name: "test"},
},
}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
}
package executor
import "errors"
var (
errEmptyName = errors.New("errEmptyName")
errDuplicateMember = errors.New("errDuplicateMember")
errDuplicateAdmin = errors.New("errDuplicateAdmin")
errInvalidVoteTime = errors.New("errInvalidVoteTime")
errInvalidVoteOption = errors.New("errInvalidVoteOption")
errVoteNotExist = errors.New("errVoteNotExist")
errGroupNotExist = errors.New("errGroupNotExist")
errStateDBGet = errors.New("errStateDBGet")
errInvalidVoteID = errors.New("errInvalidVoteID")
errInvalidGroupID = errors.New("errInvalidGroupID")
errInvalidOptionIndex = errors.New("errInvalidOptionIndex")
errAddrAlreadyVoted = errors.New("errAddrAlreadyVoted")
errVoteAlreadyFinished = errors.New("errVoteAlreadyFinished")
errVoteNotStarted = errors.New("errVoteNotStarted")
errVoteAlreadyClosed = errors.New("errVoteAlreadyClosed")
errAddrPermissionDenied = errors.New("errAddrPermissionDenied")
)
package executor
import (
"github.com/33cn/chain33/types"
votetypes "github.com/33cn/plugin/plugin/dapp/vote/types"
)
/*
* 实现交易的链上执行接口
* 关键数据上链(statedb)并生成交易回执(log)
*/
func (v *vote) Exec_CreateGroup(payload *votetypes.CreateGroup, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.createGroup(payload)
}
func (v *vote) Exec_UpdateGroup(payload *votetypes.UpdateGroup, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.updateGroup(payload)
}
func (v *vote) Exec_CreateVote(payload *votetypes.CreateVote, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.createVote(payload)
}
func (v *vote) Exec_CommitVote(payload *votetypes.CommitVote, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.commitVote(payload)
}
func (v *vote) Exec_CloseVote(payload *votetypes.CloseVote, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.closeVote(payload)
}
func (v *vote) Exec_UpdateMember(payload *votetypes.UpdateMember, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newAction(v, tx, index)
return action.updateMember(payload)
}
package executor
import (
"github.com/33cn/chain33/types"
)
/*
* 实现区块回退时本地执行的数据清除
*/
// ExecDelLocal localdb kv数据自动回滚接口
func (v *vote) ExecDelLocal(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
kvs, err := v.DelRollbackKV(tx, tx.Execer)
if err != nil {
return nil, err
}
dbSet := &types.LocalDBSet{}
dbSet.KV = append(dbSet.KV, kvs...)
return dbSet, nil
}
package executor
import (
"encoding/hex"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
/*
* 实现交易相关数据本地执行,数据不上链
* 非关键数据,本地存储(localDB), 用于辅助查询,效率高
*/
func (v *vote) ExecLocal_CreateGroup(payload *vty.CreateGroup, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
groupInfo := decodeGroupInfo(receiptData.Logs[0].Log)
table := newGroupTable(v.GetLocalDB())
kvs, err := v.updateAndSaveTable(table.Add, table.Save, groupInfo, tx, vty.NameCreateGroupAction, "group")
if err != nil {
return nil, err
}
dbSet.KV = kvs
addAddrs := make([]string, 0)
addAddrs = append(addAddrs, groupInfo.Admins...)
for _, member := range groupInfo.Members {
if !checkSliceItemExist(member.Addr, groupInfo.Admins) {
addAddrs = append(addAddrs, member.Addr)
}
}
kvs, err = v.addGroupMember(groupInfo.GetID(), addAddrs)
if err != nil {
elog.Error("execLocal createGroup", "txHash", hex.EncodeToString(tx.Hash()), "addMemberErr", err)
return nil, err
}
dbSet.KV = append(dbSet.KV, kvs...)
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
func (v *vote) ExecLocal_UpdateGroup(update *vty.UpdateGroup, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
groupInfo := decodeGroupInfo(receiptData.Logs[0].Log)
table := newGroupTable(v.GetLocalDB())
kvs, err := v.updateAndSaveTable(table.Replace, table.Save, groupInfo, tx, vty.NameUpdateGroupAction, "group")
if err != nil {
return nil, err
}
dbSet.KV = kvs
removeAddrs := make([]string, 0)
//仍然为管理员或群成员之一,不删除groupID索引
tempAddrs := append(update.RemoveAdmins, update.RemoveMembers...)
for _, addr := range tempAddrs {
if checkMemberExist(addr, groupInfo.Members) || checkSliceItemExist(addr, groupInfo.Admins) {
continue
}
removeAddrs = append(removeAddrs, addr)
}
kvs, err = v.removeGroupMember(groupInfo.GetID(), removeAddrs)
if err != nil {
elog.Error("execLocal UpdateGroup", "txHash", hex.EncodeToString(tx.Hash()), "removeMemberErr", err)
return nil, err
}
dbSet.KV = append(dbSet.KV, kvs...)
addAddrs := make([]string, 0)
addAddrs = append(addAddrs, update.AddAdmins...)
for _, member := range update.AddMembers {
if !checkSliceItemExist(member.Addr, update.AddAdmins) {
addAddrs = append(addAddrs, member.Addr)
}
}
kvs, err = v.addGroupMember(groupInfo.GetID(), addAddrs)
if err != nil {
elog.Error("execLocal UpdateGroup", "txHash", hex.EncodeToString(tx.Hash()), "addMemberErr", err)
return nil, err
}
dbSet.KV = append(dbSet.KV, kvs...)
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
func (v *vote) ExecLocal_CreateVote(payload *vty.CreateVote, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
voteInfo := decodeVoteInfo(receiptData.Logs[0].Log)
table := newVoteTable(v.GetLocalDB())
kvs, err := v.updateAndSaveTable(table.Add, table.Save, voteInfo, tx, vty.NameCreateVoteAction, "vote")
if err != nil {
return nil, err
}
dbSet.KV = kvs
table = newGroupTable(v.GetLocalDB())
row, err := table.GetData([]byte(voteInfo.GroupID))
if err != nil {
elog.Error("execLocal createVote", "txHash", hex.EncodeToString(tx.Hash()), "voteTable get", err)
return nil, err
}
groupInfo, _ := row.Data.(*vty.GroupInfo)
groupInfo.VoteNum++
kvs, err = v.updateAndSaveTable(table.Replace, table.Save, groupInfo, tx, vty.NameCreateVoteAction, "group")
if err != nil {
return nil, err
}
dbSet.KV = append(dbSet.KV, kvs...)
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
func (v *vote) ExecLocal_CommitVote(payload *vty.CommitVote, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
//implement code, add customize kv to dbSet...
commitInfo := decodeCommitInfo(receiptData.Logs[0].Log)
table := newVoteTable(v.GetLocalDB())
row, err := table.GetData([]byte(payload.GetVoteID()))
if err != nil {
elog.Error("execLocal commitVote", "txHash", hex.EncodeToString(tx.Hash()), "table get", err)
return nil, err
}
voteInfo, _ := row.Data.(*vty.VoteInfo)
voteInfo.VoteOptions[payload.OptionIndex].Score += commitInfo.VoteWeight
voteInfo.CommitInfos = append(voteInfo.CommitInfos, commitInfo)
dbSet.KV, err = v.updateAndSaveTable(table.Replace, table.Save, voteInfo, tx, vty.NameCommitVoteAction, "vote")
if err != nil {
return nil, err
}
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
func (v *vote) ExecLocal_CloseVote(payload *vty.CloseVote, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
table := newVoteTable(v.GetLocalDB())
row, err := table.GetData([]byte(payload.GetVoteID()))
if err != nil {
elog.Error("execLocal closeVote", "txHash", hex.EncodeToString(tx.Hash()), "table get", err)
return nil, err
}
voteInfo, ok := row.Data.(*vty.VoteInfo)
if !ok {
elog.Error("execLocal closeVote", "txHash", hex.EncodeToString(tx.Hash()), "voteInfo type asset", err)
return nil, types.ErrTypeAsset
}
voteInfo.Status = voteStatusClosed
dbSet.KV, err = v.updateAndSaveTable(table.Replace, table.Save, voteInfo, tx, vty.NameCloseVoteAction, "vote")
if err != nil {
return nil, err
}
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
func (v *vote) ExecLocal_UpdateMember(payload *vty.UpdateMember, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
table := newMemberTable(v.GetLocalDB())
row, err := table.GetData([]byte(tx.From()))
if err != nil {
elog.Error("execLocal updateMember", "txHash", hex.EncodeToString(tx.Hash()), "table get", err)
return nil, err
}
memberInfo, _ := row.Data.(*vty.MemberInfo)
memberInfo.Name = payload.GetName()
dbSet.KV, err = v.updateAndSaveTable(table.Replace, table.Save, memberInfo, tx, vty.NameUpdateMemberAction, "member")
if err != nil {
return nil, err
}
//auto gen for localdb auto rollback
return v.addAutoRollBack(tx, dbSet.KV), nil
}
//当区块回滚时,框架支持自动回滚localdb kv,需要对exec-local返回的kv进行封装
func (v *vote) addAutoRollBack(tx *types.Transaction, kv []*types.KeyValue) *types.LocalDBSet {
dbSet := &types.LocalDBSet{}
dbSet.KV = v.AddRollbackKV(tx, tx.Execer, kv)
return dbSet
}
type updateFunc func(message types.Message) error
type saveFunc func() ([]*types.KeyValue, error)
func (v *vote) updateAndSaveTable(update updateFunc, save saveFunc, data types.Message, tx *types.Transaction, actionName, tableName string) ([]*types.KeyValue, error) {
err := update(data)
if err != nil {
elog.Error("execLocal "+actionName, "txHash", hex.EncodeToString(tx.Hash()), tableName+" table update", err)
return nil, err
}
kvs, err := save()
if err != nil {
elog.Error("execLocal "+actionName, "txHash", hex.EncodeToString(tx.Hash()), tableName+" table save", err)
return nil, err
}
return kvs, nil
}
// 新增用户时,将对应的groupID信息添加到用户表中
func (v *vote) addGroupMember(groupID string, addrs []string) ([]*types.KeyValue, error) {
table := newMemberTable(v.GetLocalDB())
for _, addr := range addrs {
addrKey := []byte(addr)
row, err := table.GetData(addrKey)
if err == nil {
info, _ := row.Data.(*vty.MemberInfo)
if !checkSliceItemExist(groupID, info.GroupIDs) {
info.GroupIDs = append(info.GroupIDs, groupID)
err = table.Replace(info)
}
} else if err == types.ErrNotFound {
err = table.Add(&vty.MemberInfo{Addr: addr, GroupIDs: []string{groupID}})
}
// 这个错可能由GetData,Replace,Add返回
if err != nil {
elog.Error("execLocal addMember", "member table Add/Replace", err)
return nil, err
}
}
kvs, err := table.Save()
if err != nil {
elog.Error("execLocal addMember", "member table save", err)
return nil, err
}
return kvs, nil
}
//删除用户,将对应的groupID信息删除
func (v *vote) removeGroupMember(groupID string, addrs []string) ([]*types.KeyValue, error) {
table := newMemberTable(v.GetLocalDB())
for _, addr := range addrs {
addrKey := []byte(addr)
row, err := table.GetData(addrKey)
if err == types.ErrNotFound {
continue
} else if err != nil {
elog.Error("execLocal removeMember", "member table getData", err)
return nil, err
}
info, _ := row.Data.(*vty.MemberInfo)
for index, id := range info.GroupIDs {
if id == groupID {
info.GroupIDs = append(info.GroupIDs[:index], info.GroupIDs[index+1:]...)
err = table.Replace(info)
if err != nil {
elog.Error("execLocal removeMember", "member table replace", err)
return nil, err
}
break
}
}
}
kvs, err := table.Save()
if err != nil {
elog.Error("execLocal addMember", "member table save", err)
return nil, err
}
return kvs, nil
}
package executor
import (
"testing"
"github.com/33cn/chain33/system/dapp"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/33cn/chain33/common/crypto"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/stretchr/testify/require"
)
const (
testTypeCheckTx = iota + 1
testTypeExec
testTypeExecLocal
testTypeExecDelLocal
)
func testExec(t *testing.T, mock *testExecMock, testExecType int, tcArr []*testcase, priv crypto.PrivKey) {
if mock == nil {
mock = &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
}
exec := mock.exec
for i, tc := range tcArr {
signPriv := priv
if tc.priv != nil {
signPriv = tc.priv
}
tx, err := createTx(mock, tc.payload, signPriv)
require.NoErrorf(t, err, "createTxErr, testIndex=%d", tc.index)
if err != nil {
continue
}
err = exec.CheckTx(tx, i)
require.Equalf(t, tc.expectCheckErr, err, "checkTx err index %d", tc.index)
execType := testExecType
if tc.execType > 0 {
execType = tc.execType
}
if execType == testTypeCheckTx {
continue
}
recp, err := exec.Exec(tx, i)
recpData := &types.ReceiptData{
Ty: recp.GetTy(),
Logs: recp.GetLogs(),
}
if err == nil && len(recp.GetKV()) > 0 {
util.SaveKVList(mock.stateDB, recp.KV)
}
require.Equalf(t, tc.expectExecErr, err, "execTx err index %d", tc.index)
if execType == testTypeExec {
continue
}
kvSet, err := exec.ExecLocal(tx, recpData, i)
for _, kv := range kvSet.GetKV() {
err := mock.localDB.Set(kv.Key, kv.Value)
require.Nil(t, err)
}
require.Equalf(t, tc.expectExecLocalErr, err, "execLocalTx err index %d", tc.index)
if execType == testTypeExecLocal {
continue
}
kvSet, err = exec.ExecDelLocal(tx, recpData, i)
for _, kv := range kvSet.GetKV() {
err := mock.localDB.Set(kv.Key, kv.Value)
require.Nil(t, err)
}
require.Equalf(t, tc.expectExecDelErr, err, "execDelLocalTx err index %d", tc.index)
}
}
func TestVote_Exec(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
}, {
index: 1,
payload: &vty.CreateVote{
Name: "vote",
GroupID: groupID,
EndTimestamp: testBlockTime + 1,
VoteOptions: []string{"A", "B"},
},
}, {
index: 2,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: testAddrs, AddAdmins: []string{testAddrs[1]}},
}, {
index: 3,
payload: &vty.CommitVote{VoteID: voteID},
}, {
index: 4,
payload: &vty.UpdateGroup{GroupID: groupID, AddAdmins: []string{testAddrs[0]}},
expectCheckErr: errAddrPermissionDenied,
execType: testTypeCheckTx,
}, {
index: 5,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: testAddrs, AddAdmins: []string{testAddrs[0]}},
priv: privKeys[1],
}, {
index: 6,
payload: &vty.UpdateGroup{GroupID: groupID, AddMembers: []*vty.GroupMember{{Addr: testAddrs[1]}}, RemoveMembers: testAddrs},
}, {
index: 7,
payload: &vty.CommitVote{VoteID: voteID},
expectCheckErr: errAddrPermissionDenied,
execType: testTypeCheckTx,
}, {
index: 8,
payload: &vty.CommitVote{VoteID: voteID},
priv: privKeys[1],
}, {
index: 9,
payload: &vty.CloseVote{VoteID: voteID},
}, {
index: 10,
payload: &vty.CloseVote{VoteID: voteID},
execType: testTypeCheckTx,
expectCheckErr: errVoteAlreadyClosed,
}, {
index: 11,
payload: &vty.UpdateMember{Name: "testName"},
},
}
testExec(t, nil, testTypeExec, tcArr, privKeys[0])
}
package executor
import (
"testing"
tab "github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/stretchr/testify/require"
)
type tableCase struct {
index int
key []byte
expectGetErr error
expectData types.Message
}
func testTableData(t *testing.T, table *tab.Table, tcArr []*tableCase, msg string) {
for _, tc := range tcArr {
row, err := table.GetData(tc.key)
require.Equalf(t, tc.expectGetErr, err, msg+",index=%d", tc.index)
if err != nil {
continue
}
require.Equalf(t, tc.expectData.String(), row.Data.String(),
msg+",index=%d", tc.index)
}
}
func TestVote_ExecLocal_CreateGroup(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
groupID2 := formatGroupID(dapp.HeightIndexStr(testHeight, 1))
members1 := []*vty.GroupMember{{Addr: testAddrs[1], VoteWeight: 1}}
members2 := []*vty.GroupMember{{Addr: testAddrs[2], VoteWeight: 1}}
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{
Name: "test",
Members: members1},
}, {
index: 1,
payload: &vty.CreateGroup{
Name: "test",
Members: members2},
},
}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newMemberTable(mock.exec.GetLocalDB())
tcArr1 := []*tableCase{{
index: 0,
key: []byte(testAddrs[0]),
expectData: &vty.MemberInfo{Addr: testAddrs[0], GroupIDs: []string{groupID, groupID2}},
}, {
index: 1,
key: []byte(testAddrs[1]),
expectData: &vty.MemberInfo{Addr: testAddrs[1], GroupIDs: []string{groupID}},
}, {
index: 2,
key: []byte(testAddrs[2]),
expectData: &vty.MemberInfo{Addr: testAddrs[2], GroupIDs: []string{groupID2}},
}, {
index: 3,
key: []byte("addr"),
expectGetErr: types.ErrNotFound,
}}
testTableData(t, table, tcArr1, "check member groupIDs")
table = newGroupTable(mock.exec.GetLocalDB())
tcArr1 = []*tableCase{{
index: 0,
key: []byte(groupID),
expectData: &vty.GroupInfo{ID: groupID, Name: "test", MemberNum: 1,
Admins: []string{testAddrs[0]}, Creator: testAddrs[0], Members: members1},
}, {
index: 1,
key: []byte(groupID2),
expectData: &vty.GroupInfo{ID: groupID2, Name: "test", MemberNum: 1,
Admins: []string{testAddrs[0]}, Creator: testAddrs[0], Members: members2},
}}
testTableData(t, table, tcArr1, "check groupInfo")
}
func TestVote_ExecLocal_UpdateGroup(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
members := []*vty.GroupMember{{Addr: testAddrs[2], VoteWeight: 1}}
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: []string{testAddrs[0]}, AddAdmins: []string{testAddrs[1]}},
}, {
index: 2,
priv: privKeys[1],
payload: &vty.UpdateGroup{GroupID: groupID, RemoveMembers: testAddrs, AddMembers: members},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newMemberTable(mock.exec.GetLocalDB())
tcArr1 := []*tableCase{{
index: 0,
key: []byte(testAddrs[0]),
expectData: &vty.MemberInfo{Addr: testAddrs[0]},
}, {
index: 1,
key: []byte(testAddrs[1]),
expectData: &vty.MemberInfo{Addr: testAddrs[1], GroupIDs: []string{groupID}},
}, {
index: 2,
key: []byte(testAddrs[2]),
expectData: &vty.MemberInfo{Addr: testAddrs[2], GroupIDs: []string{groupID}},
}}
testTableData(t, table, tcArr1, "check member groupIDs")
table = newGroupTable(mock.exec.GetLocalDB())
expectInfo := &vty.GroupInfo{ID: groupID, Name: "test", Admins: []string{testAddrs[1]},
Members: members, MemberNum: 1, Creator: testAddrs[0]}
testTableData(t, table, []*tableCase{{
index: 0,
key: []byte(groupID),
expectData: expectInfo,
}, {
index: 1,
key: []byte("testid"),
expectGetErr: types.ErrNotFound,
}}, "check group Info")
tx := util.CreateNoneTx(mock.cfg, privKeys[0])
group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID)
require.Nil(t, err)
require.Equal(t, group.String(), expectInfo.String())
}
func TestVote_ExecLocal_CreateVote(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
options := []*vty.VoteOption{{Option: "A"}, {Option: "B"}}
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newVoteTable(mock.exec.GetLocalDB())
expectVoteInfo := &vty.VoteInfo{
Name: "test", VoteOptions: options, BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1,
GroupID: groupID, ID: voteID, Creator: testAddrs[0],
}
testTableData(t, table, []*tableCase{{
index: 0,
key: []byte(voteID),
expectData: expectVoteInfo,
}}, "check vote Info")
table = newGroupTable(mock.exec.GetLocalDB())
row, err := table.GetData([]byte(groupID))
require.Nil(t, err)
info, _ := row.Data.(*vty.GroupInfo)
require.Equal(t, uint32(1), info.VoteNum)
tx := util.CreateNoneTx(mock.cfg, privKeys[0])
group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID)
require.Nil(t, err)
group.VoteNum = info.VoteNum
require.Equal(t, group.String(), info.String())
}
func TestVote_ExecLocal_CloseVote(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}, {
index: 2,
payload: &vty.CloseVote{VoteID: voteID},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newVoteTable(mock.exec.GetLocalDB())
row, err := table.GetData([]byte(voteID))
require.Nil(t, err)
info, _ := row.Data.(*vty.VoteInfo)
require.Equal(t, uint32(voteStatusClosed), info.Status)
tx := util.CreateNoneTx(mock.cfg, privKeys[0])
vote, err := newAction(mock.exec, tx, 0).getVoteInfo(voteID)
require.Nil(t, err)
require.Equal(t, vote.String(), info.String())
}
func TestVote_ExecLocal_CommitVote(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
members := []*vty.GroupMember{{Addr: testAddrs[0], VoteWeight: 1}}
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: members},
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}, {
index: 2,
payload: &vty.CommitVote{VoteID: voteID},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newVoteTable(mock.exec.GetLocalDB())
row, err := table.GetData([]byte(voteID))
require.Nil(t, err)
info, _ := row.Data.(*vty.VoteInfo)
require.Equal(t, testAddrs[0], info.CommitInfos[0].Addr)
require.Equal(t, uint32(1), info.VoteOptions[0].Score)
tx := util.CreateNoneTx(mock.cfg, privKeys[0])
vote, err := newAction(mock.exec, tx, 0).getVoteInfo(voteID)
require.Nil(t, err)
vote.CommitInfos[0].TxHash = info.CommitInfos[0].TxHash
vote.CommitInfos[0].VoteWeight = info.CommitInfos[0].VoteWeight
require.Equal(t, vote.String(), info.String())
}
func TestVote_ExecDelLocal(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
execType: testTypeExecLocal,
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}}
testExec(t, mock, testTypeExecDelLocal, tcArr, privKeys[0])
table := newVoteTable(mock.exec.GetLocalDB())
_, err := table.GetData([]byte(voteID))
require.Equal(t, types.ErrDecode, err)
}
package executor
/*
* 用户合约存取kv数据时,key值前缀需要满足一定规范
* 即key = keyPrefix + userKey
* 需要字段前缀查询时,使用’-‘作为分割符号
*/
var (
//keyPrefixStateDB state db key必须前缀
keyPrefixStateDB = "mavl-vote-"
//keyPrefixLocalDB local db的key必须前缀
keyPrefixLocalDB = "LODB-vote-"
)
// groupID or voteID
func formatStateIDKey(id string) []byte {
return []byte(keyPrefixStateDB + id)
}
package executor
import (
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
func (v *vote) getGroup(groupID string) (*vty.GroupInfo, error) {
if len(groupID) != IDLen {
return nil, errInvalidGroupID
}
table := newGroupTable(v.GetLocalDB())
row, err := table.GetData([]byte(groupID))
if err != nil {
elog.Error("query getGroup", "groupID", groupID, "err", err)
return nil, err
}
info, ok := row.Data.(*vty.GroupInfo)
if !ok {
return nil, types.ErrTypeAsset
}
return info, nil
}
// Query_GroupInfo query group info
func (v *vote) Query_GetGroups(in *vty.ReqStrings) (types.Message, error) {
if len(in.GetItems()) == 0 {
return nil, types.ErrInvalidParam
}
infos := &vty.GroupInfos{GroupList: make([]*vty.GroupInfo, 0, len(in.GetItems()))}
for _, id := range in.GetItems() {
info, err := v.getGroup(id)
if err != nil {
return nil, err
}
infos.GroupList = append(infos.GroupList, info)
}
return infos, nil
}
func (v *vote) getVote(voteID string) (*vty.VoteInfo, error) {
if len(voteID) != IDLen {
return nil, errInvalidVoteID
}
table := newVoteTable(v.GetLocalDB())
row, err := table.GetData([]byte(voteID))
if err != nil {
elog.Error("query getVote", "id", voteID, "err", err)
return nil, err
}
info, ok := row.Data.(*vty.VoteInfo)
if !ok {
return nil, types.ErrTypeAsset
}
return info, nil
}
func (v *vote) Query_GetVotes(in *vty.ReqStrings) (types.Message, error) {
if len(in.GetItems()) == 0 {
return nil, types.ErrInvalidParam
}
voteList := make([]*vty.VoteInfo, 0, len(in.GetItems()))
for _, id := range in.GetItems() {
info, err := v.getVote(id)
if err != nil {
return nil, err
}
voteList = append(voteList, info)
}
reply := &vty.ReplyVoteList{CurrentTimestamp: types.Now().Unix()}
reply.VoteList = filterVoteWithStatus(voteList, 0, reply.CurrentTimestamp)
return reply, nil
}
func (v *vote) getMember(addr string) (*vty.MemberInfo, error) {
if len(addr) != addrLen {
return nil, types.ErrInvalidAddress
}
table := newMemberTable(v.GetLocalDB())
row, err := table.GetData([]byte(addr))
if err != nil {
elog.Error("query getMember", "addr", addr, "err", err)
return nil, err
}
info, ok := row.Data.(*vty.MemberInfo)
if !ok {
return nil, types.ErrTypeAsset
}
return info, nil
}
func (v *vote) Query_GetMembers(in *vty.ReqStrings) (types.Message, error) {
if len(in.GetItems()) == 0 {
return nil, types.ErrInvalidParam
}
infos := &vty.MemberInfos{MemberList: make([]*vty.MemberInfo, 0, len(in.GetItems()))}
for _, id := range in.GetItems() {
info, err := v.getMember(id)
if err != nil {
return nil, err
}
infos.MemberList = append(infos.MemberList, info)
}
return infos, nil
}
func (v *vote) Query_ListGroup(in *vty.ReqListItem) (types.Message, error) {
if in == nil {
return nil, types.ErrInvalidParam
}
table := newGroupTable(v.GetLocalDB())
var primaryKey []byte
primaryKey = append(primaryKey, []byte(in.StartItemID)...)
list := &vty.GroupInfos{}
rows, err := table.ListIndex(groupTablePrimary, nil, primaryKey, in.Count, in.Direction)
// 已经没有数据,直接返回
if err == types.ErrNotFound {
return list, nil
}
if err != nil {
elog.Error("query listGroup", "err", err, "param", in)
return nil, err
}
list.GroupList = make([]*vty.GroupInfo, 0, len(rows))
for _, row := range rows {
info, ok := row.Data.(*vty.GroupInfo)
if !ok {
return nil, types.ErrTypeAsset
}
list.GroupList = append(list.GroupList, info)
}
return list, nil
}
func (v *vote) Query_ListVote(in *vty.ReqListVote) (types.Message, error) {
if in.GetListReq() == nil {
return nil, types.ErrInvalidParam
}
table := newVoteTable(v.GetLocalDB())
//指定了组ID,则查询对应组下的投票列表
groupID := in.GetGroupID()
indexName := voteTablePrimary
var prefix, primaryKey []byte
if len(groupID) > 0 {
indexName = groupTablePrimary
prefix = []byte(groupID)
}
primaryKey = append(primaryKey, []byte(in.GetListReq().GetStartItemID())...)
reply := &vty.ReplyVoteList{CurrentTimestamp: types.Now().Unix()}
listCount := in.ListReq.GetCount()
listMore:
rows, err := table.ListIndex(indexName, prefix, primaryKey, listCount, in.GetListReq().Direction)
// 已经没有数据,直接返回
if err == types.ErrNotFound {
return reply, nil
}
if err != nil {
elog.Error("query listVote", "err", err, "param", in)
return nil, err
}
list := make([]*vty.VoteInfo, 0, len(rows))
for _, row := range rows {
info, ok := row.Data.(*vty.VoteInfo)
if !ok {
return nil, types.ErrTypeAsset
}
list = append(list, info)
}
primaryKey = append(primaryKey[:0], []byte(list[len(list)-1].ID)...)
list = filterVoteWithStatus(list, in.Status, reply.CurrentTimestamp)
reply.VoteList = append(reply.VoteList, list...)
//经过筛选后,数量小于请求数量,则需要再次list, 需要满足len(rows)==listCount, 否则表示已经没有数据
if len(rows) == int(listCount) && int(listCount) > len(list) {
listCount -= int32(len(list))
goto listMore
}
return reply, nil
}
func (v *vote) Query_ListMember(in *vty.ReqListItem) (types.Message, error) {
if in == nil {
return nil, types.ErrInvalidParam
}
table := newMemberTable(v.GetLocalDB())
var primaryKey []byte
primaryKey = append(primaryKey, []byte(in.StartItemID)...)
list := &vty.MemberInfos{}
rows, err := table.ListIndex(memberTablePrimary, nil, primaryKey, in.Count, in.Direction)
// 已经没有数据,直接返回
if err == types.ErrNotFound {
return list, nil
}
if err != nil {
elog.Error("query listMember", "err", err, "param", in)
return nil, err
}
list.MemberList = make([]*vty.MemberInfo, 0, len(rows))
for _, row := range rows {
info, ok := row.Data.(*vty.MemberInfo)
if !ok {
return nil, types.ErrTypeAsset
}
list.MemberList = append(list.MemberList, info)
}
return list, nil
}
package executor
import (
"testing"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/stretchr/testify/require"
)
func TestVote_Query_GetGroups(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
groupID2 := formatGroupID(dapp.HeightIndexStr(testHeight, 1))
groupID3 := formatGroupID(dapp.HeightIndexStr(testHeight, 2))
groupIDs := []string{groupID, groupID2, groupID3, "testid"}
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateGroup{Name: "test"},
},
}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "GetGroups"
_, err := exec.Query(funcName, nil)
require.Equal(t, types.ErrInvalidParam, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: groupIDs[3:]}))
require.Equal(t, errInvalidGroupID, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: groupIDs[:3]}))
require.Equal(t, types.ErrNotFound, err)
data, err := exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: groupIDs[:2]}))
require.Equal(t, nil, err)
groups := data.(*vty.GroupInfos)
require.Equal(t, 2, len(groups.GroupList))
require.Equal(t, groupID, groups.GroupList[0].ID)
require.Equal(t, groupID2, groups.GroupList[1].ID)
}
func TestVote_Query_GetVotes(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
voteID2 := formatVoteID(dapp.HeightIndexStr(testHeight, 2))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "GetVotes"
_, err := exec.Query(funcName, nil)
require.Equal(t, types.ErrInvalidParam, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{voteID2}}))
require.Equal(t, types.ErrNotFound, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{"voteid"}}))
require.Equal(t, errInvalidVoteID, err)
data, err := exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{voteID}}))
require.Equal(t, nil, err)
vote := data.(*vty.ReplyVoteList)
require.Equal(t, voteID, vote.VoteList[0].ID)
}
func TestVote_Query_GetMembers(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "GetMembers"
_, err := exec.Query(funcName, nil)
require.Equal(t, types.ErrInvalidParam, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{testAddrs[1]}}))
require.Equal(t, types.ErrNotFound, err)
_, err = exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{"addr"}}))
require.Equal(t, types.ErrInvalidAddress, err)
data, err := exec.Query(funcName, types.Encode(&vty.ReqStrings{Items: []string{testAddrs[0]}}))
require.Equal(t, nil, err)
members := data.(*vty.MemberInfos)
require.Equal(t, testAddrs[0], members.MemberList[0].Addr)
require.Equal(t, []string{groupID}, members.MemberList[0].GroupIDs)
}
func TestVote_Query_ListGroup(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
groupID2 := formatGroupID(dapp.HeightIndexStr(testHeight, 1))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateGroup{Name: "test"},
},
}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "ListGroup"
data, err := exec.Query(funcName, nil)
require.Equal(t, nil, err)
list := data.(*vty.GroupInfos)
require.Equal(t, 2, len(list.GroupList))
data, err = exec.Query(funcName, types.Encode(&vty.ReqListItem{Count: 1, Direction: 1}))
require.Equal(t, nil, err)
list = data.(*vty.GroupInfos)
require.Equal(t, 1, len(list.GroupList))
require.Equal(t, groupID, list.GroupList[0].ID)
data, err = exec.Query(funcName, types.Encode(&vty.ReqListItem{StartItemID: groupID2}))
require.Equal(t, nil, err)
list = data.(*vty.GroupInfos)
require.Equal(t, 1, len(list.GroupList))
require.Equal(t, groupID, list.GroupList[0].ID)
}
func TestVote_Query_ListVote(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
now := types.Now().Unix() + 1000
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test"},
}, {
index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
EndTimestamp: testBlockTime + 1},
}, {
index: 2,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: now, EndTimestamp: now + 1},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "ListVote"
_, err := exec.Query(funcName, nil)
require.Equal(t, types.ErrInvalidParam, err)
data, err := exec.Query(funcName, types.Encode(&vty.ReqListVote{GroupID: groupID, ListReq: &vty.ReqListItem{}}))
require.Nil(t, err)
list := data.(*vty.ReplyVoteList)
require.Equal(t, 2, len(list.VoteList))
data, err = exec.Query(funcName, types.Encode(&vty.ReqListVote{GroupID: groupID, Status: voteStatusPending, ListReq: &vty.ReqListItem{}}))
require.Nil(t, err)
list = data.(*vty.ReplyVoteList)
require.Equal(t, 1, len(list.VoteList))
require.Equal(t, uint32(voteStatusPending), list.VoteList[0].Status)
}
func TestVote_Query_ListMember(t *testing.T) {
mock := &testExecMock{}
mock.InitEnv()
defer mock.FreeEnv()
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
tcArr := []*testcase{{
index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
}}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
exec := mock.exec
funcName := "ListMember"
data, err := exec.Query(funcName, nil)
require.Equal(t, nil, err)
list := data.(*vty.MemberInfos)
require.Equal(t, 1, len(list.MemberList))
require.Equal(t, testAddrs[0], list.MemberList[0].Addr)
require.Equal(t, []string{groupID}, list.MemberList[0].GroupIDs)
data, err = exec.Query(funcName, types.Encode(&vty.ReqListItem{StartItemID: "addr"}))
require.Equal(t, nil, err)
list = data.(*vty.MemberInfos)
require.Equal(t, &vty.MemberInfos{}, list)
}
package executor
import (
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
var (
groupTablePrimary = "groupid"
voteTablePrimary = "voteid"
memberTablePrimary = "addr"
)
var groupTableOpt = &table.Option{
Prefix: keyPrefixLocalDB,
Name: "group",
Primary: groupTablePrimary,
}
var voteTableOpt = &table.Option{
Prefix: keyPrefixLocalDB,
Name: "vote",
Primary: voteTablePrimary,
Index: []string{groupTablePrimary},
}
var memberTableOpt = &table.Option{
Prefix: keyPrefixLocalDB,
Name: "member",
Primary: memberTablePrimary,
}
//新建表
func newTable(kvDB db.KV, rowMeta table.RowMeta, opt *table.Option) *table.Table {
table, err := table.NewTable(rowMeta, kvDB, opt)
if err != nil {
panic(err)
}
return table
}
func newGroupTable(kvDB db.KV) *table.Table {
return newTable(kvDB, &groupTableRow{}, groupTableOpt)
}
func newVoteTable(kvDB db.KV) *table.Table {
return newTable(kvDB, &voteTableRow{}, voteTableOpt)
}
func newMemberTable(kvDB db.KV) *table.Table {
return newTable(kvDB, &memberTableRow{}, memberTableOpt)
}
//groupTableRow table meta 结构
type groupTableRow struct {
*vty.GroupInfo
}
//CreateRow 新建数据行
func (r *groupTableRow) CreateRow() *table.Row {
return &table.Row{Data: &vty.GroupInfo{}}
}
//SetPayload 设置数据
func (r *groupTableRow) SetPayload(data types.Message) error {
if d, ok := data.(*vty.GroupInfo); ok {
r.GroupInfo = d
return nil
}
return types.ErrTypeAsset
}
//Get 按照indexName 查询 indexValue
func (r *groupTableRow) Get(key string) ([]byte, error) {
if key == groupTablePrimary {
return []byte(r.GroupInfo.GetID()), nil
}
return nil, types.ErrNotFound
}
//voteTableRow table meta 结构
type voteTableRow struct {
*vty.VoteInfo
}
//CreateRow 新建数据行
func (r *voteTableRow) CreateRow() *table.Row {
return &table.Row{Data: &vty.VoteInfo{}}
}
//SetPayload 设置数据
func (r *voteTableRow) SetPayload(data types.Message) error {
if d, ok := data.(*vty.VoteInfo); ok {
r.VoteInfo = d
return nil
}
return types.ErrTypeAsset
}
//Get 按照indexName 查询 indexValue
func (r *voteTableRow) Get(key string) ([]byte, error) {
if key == voteTablePrimary {
return []byte(r.VoteInfo.GetID()), nil
} else if key == groupTablePrimary {
return []byte(r.VoteInfo.GetGroupID()), nil
}
return nil, types.ErrNotFound
}
type memberTableRow struct {
*vty.MemberInfo
}
//CreateRow 新建数据行
func (r *memberTableRow) CreateRow() *table.Row {
return &table.Row{Data: &vty.MemberInfo{}}
}
//SetPayload 设置数据
func (r *memberTableRow) SetPayload(data types.Message) error {
if d, ok := data.(*vty.MemberInfo); ok {
r.MemberInfo = d
return nil
}
return types.ErrTypeAsset
}
//Get 按照indexName 查询 indexValue
func (r *memberTableRow) Get(key string) ([]byte, error) {
if key == memberTablePrimary {
return []byte(r.MemberInfo.GetAddr()), nil
}
return nil, types.ErrNotFound
}
package executor
import (
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
)
const (
voteStatusNormal = iota //非关闭常规状态
voteStatusPending //即将开始
voteStatusOngoing //正在进行
voteStatusFinished //已经结束
voteStatusClosed //已经关闭
)
const (
// IDLen length of groupID or voteID
IDLen = 19
addrLen = 34
)
func formatGroupID(id string) string {
return "g" + id
}
func formatVoteID(id string) string {
return "v" + id
}
func checkMemberExist(addr string, members []*vty.GroupMember) bool {
for _, item := range members {
if addr == item.Addr {
return true
}
}
return false
}
func checkSliceItemExist(target string, items []string) bool {
for _, item := range items {
if target == item {
return true
}
}
return false
}
func checkSliceItemDuplicate(items []string) bool {
filter := make(map[string]struct{}, len(items))
for _, item := range items {
if _, ok := filter[item]; ok {
return true
}
filter[item] = struct{}{}
}
return false
}
func readStateDB(stateDB db.KV, key []byte, result types.Message) error {
val, err := stateDB.Get(key)
if err != nil {
return err
}
return types.Decode(val, result)
}
func mustDecodeProto(data []byte, msg types.Message) {
if err := types.Decode(data, msg); err != nil {
panic(err.Error())
}
}
func decodeGroupInfo(data []byte) *vty.GroupInfo {
info := &vty.GroupInfo{}
mustDecodeProto(data, info)
return info
}
func decodeVoteInfo(data []byte) *vty.VoteInfo {
info := &vty.VoteInfo{}
mustDecodeProto(data, info)
return info
}
func decodeCommitInfo(data []byte) *vty.CommitInfo {
info := &vty.CommitInfo{}
mustDecodeProto(data, info)
return info
}
func filterVoteWithStatus(voteList []*vty.VoteInfo, status uint32, currentTime int64) []*vty.VoteInfo {
var filterList []*vty.VoteInfo
for _, voteInfo := range voteList {
if voteInfo.Status == voteStatusClosed {
} else if voteInfo.BeginTimestamp > currentTime {
voteInfo.Status = voteStatusPending
} else if voteInfo.EndTimestamp > currentTime {
voteInfo.Status = voteStatusOngoing
} else {
voteInfo.Status = voteStatusFinished
}
//remove vote info with other status
if status == voteInfo.Status {
filterList = append(filterList, voteInfo)
}
}
//设置了状态筛选,返回对应的筛选列表
if status > 0 {
return filterList
}
return voteList
}
package executor
import (
"errors"
"testing"
"github.com/33cn/chain33/client"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/crypto"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/log"
"github.com/33cn/chain33/queue"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
wcom "github.com/33cn/chain33/wallet/common"
vty "github.com/33cn/plugin/plugin/dapp/vote/types"
"github.com/stretchr/testify/require"
)
var (
testHeight = int64(100)
testBlockTime = int64(1539918074)
// 测试的私钥
testPrivateKeys = []string{
"0x8dea7332c7bb3e3b0ce542db41161fd021e3cfda9d7dabacf24f98f2dfd69558",
"0x920976ffe83b5a98f603b999681a0bc790d97e22ffc4e578a707c2234d55cc8a",
"0xb59f2b02781678356c231ad565f73699753a28fd3226f1082b513ebf6756c15c",
}
// 测试的地址
testAddrs = []string{
"1EDDghAtgBsamrNEtNmYdQzC1QEhLkr87t",
"13cS5G1BDN2YfGudsxRxr7X25yu6ZdgxMU",
"1JSRSwp16NvXiTjYBYK9iUQ9wqp3sCxz2p",
}
// 测试的隐私公钥对
testPubkeyPairs = []string{
"92fe6cfec2e19cd15f203f83b5d440ddb63d0cb71559f96dc81208d819fea85886b08f6e874fca15108d244b40f9086d8c03260d4b954a40dfb3cbe41ebc7389",
"6326126c968a93a546d8f67d623ad9729da0e3e4b47c328a273dfea6930ffdc87bcc365822b80b90c72d30e955e7870a7a9725e9a946b9e89aec6db9455557eb",
"44bf54abcbae297baf3dec4dd998b313eafb01166760f0c3a4b36509b33d3b50239de0a5f2f47c2fc98a98a382dcd95a2c5bf1f4910467418a3c2595b853338e",
}
privKeys = make([]crypto.PrivKey, len(testPrivateKeys))
testCfg = types.NewChain33Config(types.GetDefaultCfgstring())
)
func init() {
log.SetLogLevel("error")
Init(vty.VoteX, testCfg, nil)
for i, priv := range testPrivateKeys {
privKeys[i], _ = decodePrivKey(priv)
}
}
type testExecMock struct {
dbDir string
localDB dbm.KVDB
stateDB dbm.DB
exec *vote
policy wcom.WalletBizPolicy
cfg *types.Chain33Config
q queue.Queue
qapi client.QueueProtocolAPI
execType types.ExecutorType
}
type testcase struct {
payload types.Message
expectExecErr error
expectCheckErr error
expectExecLocalErr error
expectExecDelErr error
priv crypto.PrivKey
execType int
index int
}
// InitEnv init env
func (mock *testExecMock) InitEnv() {
mock.cfg = testCfg
util.ResetDatadir(mock.cfg.GetModuleConfig(), "$TEMP/")
mock.q = queue.New("channel")
mock.q.SetConfig(mock.cfg)
mock.qapi, _ = client.New(mock.q.Client(), nil)
mock.initExec()
}
func (mock *testExecMock) FreeEnv() {
util.CloseTestDB(mock.dbDir, mock.stateDB)
}
func (mock *testExecMock) initExec() {
mock.dbDir, mock.stateDB, mock.localDB = util.CreateTestDB()
exec := newVote()
exec.SetAPI(mock.qapi)
exec.SetStateDB(mock.stateDB)
exec.SetLocalDB(mock.localDB)
exec.SetEnv(testHeight, testBlockTime, 1539918074)
mock.exec = exec.(*vote)
mock.execType = types.LoadExecutorType(vty.VoteX)
}
func decodePrivKey(priv string) (crypto.PrivKey, error) {
c, err := crypto.New(crypto.GetName(types.SECP256K1))
if err != nil {
return nil, err
}
bytes, err := common.FromHex(priv[:])
if err != nil {
return nil, err
}
privKey, err := c.PrivKeyFromBytes(bytes)
if err != nil {
return nil, err
}
return privKey, nil
}
func createTx(mock *testExecMock, payload types.Message, privKey crypto.PrivKey) (*types.Transaction, error) {
action, _ := getActionName(payload)
tx, err := mock.execType.CreateTransaction(action, payload)
if err != nil {
return nil, errors.New("createTxErr:" + err.Error())
}
tx, err = types.FormatTx(mock.cfg, vty.VoteX, tx)
if err != nil {
return nil, errors.New("formatTxErr:" + err.Error())
}
tx.Sign(int32(types.SECP256K1), privKey)
return tx, nil
}
func getActionName(param types.Message) (string, error) {
if _, ok := param.(*vty.CreateGroup); ok {
return vty.NameCreateGroupAction, nil
} else if _, ok := param.(*vty.UpdateGroup); ok {
return vty.NameUpdateGroupAction, nil
} else if _, ok := param.(*vty.CreateVote); ok {
return vty.NameCreateVoteAction, nil
} else if _, ok := param.(*vty.CommitVote); ok {
return vty.NameCommitVoteAction, nil
} else if _, ok := param.(*vty.CloseVote); ok {
return vty.NameCloseVoteAction, nil
} else if _, ok := param.(*vty.UpdateMember); ok {
return vty.NameUpdateMemberAction, nil
} else {
return "", types.ErrActionNotSupport
}
}
func TestUtil(t *testing.T) {
heightIndex := dapp.HeightIndexStr(100, 0)
require.Equal(t, IDLen, len(formatGroupID(heightIndex)))
require.Equal(t, IDLen, len(formatVoteID(heightIndex)))
strs := []string{"a", "b", "c"}
require.True(t, checkSliceItemExist("a", strs))
require.False(t, checkSliceItemExist("d", strs))
require.False(t, checkSliceItemDuplicate(strs))
strs = append(strs, "c")
require.True(t, checkSliceItemDuplicate(strs))
members := make([]*vty.GroupMember, 0)
for _, addr := range testAddrs {
members = append(members, &vty.GroupMember{
Addr: addr,
})
}
require.True(t, checkMemberExist(testAddrs[0], members))
require.False(t, checkMemberExist("testaddr", members))
require.Equal(t, addrLen, len(testAddrs[0]))
}
func TestFilterVoteWithStatus(t *testing.T) {
currentTime := types.Now().Unix()
voteList := []*vty.VoteInfo{
{
BeginTimestamp: currentTime + 1,
},
{
EndTimestamp: currentTime + 1,
},
{
BeginTimestamp: currentTime,
EndTimestamp: currentTime,
},
{
Status: voteStatusClosed,
},
}
statusList := []uint32{voteStatusPending, voteStatusOngoing, voteStatusFinished, voteStatusClosed}
voteList = filterVoteWithStatus(voteList, 0, currentTime)
for i, info := range voteList {
require.Equal(t, statusList[i], info.Status)
}
for _, status := range statusList {
list := filterVoteWithStatus(voteList, status, currentTime)
require.Equal(t, 1, len(list))
require.Equal(t, status, list[0].Status)
}
}
package executor
import (
log "github.com/33cn/chain33/common/log/log15"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
votetypes "github.com/33cn/plugin/plugin/dapp/vote/types"
)
/*
* 执行器相关定义
* 重载基类相关接口
*/
var (
//日志
elog = log.New("module", "vote.executor")
)
var driverName = votetypes.VoteX
// Init register dapp
func Init(name string, cfg *types.Chain33Config, sub []byte) {
drivers.Register(cfg, GetName(), newVote, cfg.GetDappFork(driverName, "Enable"))
InitExecType()
}
// InitExecType Init Exec Type
func InitExecType() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&vote{}))
}
type vote struct {
drivers.DriverBase
}
func newVote() drivers.Driver {
t := &vote{}
t.SetChild(t)
t.SetExecutorType(types.LoadExecutorType(driverName))
return t
}
// GetName get driver name
func GetName() string {
return newVote().GetName()
}
func (v *vote) GetDriverName() string {
return driverName
}
package types
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/vote/commands"
"github.com/33cn/plugin/plugin/dapp/vote/executor"
"github.com/33cn/plugin/plugin/dapp/vote/rpc"
votetypes "github.com/33cn/plugin/plugin/dapp/vote/types"
)
/*
* 初始化dapp相关的组件
*/
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: votetypes.VoteX,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: commands.Cmd,
RPC: rpc.Init,
})
}
all:
bash ./create_protobuf.sh
#!/bin/bash
# proto生成命令,将pb.go文件生成到types/目录下, chain33_path支持引用chain33框架的proto文件
chain33_path=$(go list -f '{{.Dir}}' "github.com/33cn/chain33")
protoc --go_out=plugins=grpc:../types ./*.proto --proto_path=. --proto_path="${chain33_path}/types/proto/"
syntax = "proto3";
package types;
// vote 合约交易行为总类型
message VoteAction {
int32 ty = 1;
oneof value {
CreateGroup createGroup = 2; //创建投票组
UpdateGroup updateGroup = 3; //更新组成员
CreateVote createVote = 4; //创建一个投票
CommitVote commitVote = 5; //组员提交投票
CloseVote closeVote = 6; //关闭投票
UpdateMember updateMember = 7; //更新用户信息
}
}
message GroupMember {
string addr = 1; //用户地址
uint32 voteWeight = 2; //投票权重, 不填时默认为1
string nickName = 3; //群昵称
}
//创建投票组
message CreateGroup {
string name = 1; //投票组名称
repeated string admins = 2; //管理员地址列表,创建者默认为管理员
repeated GroupMember members = 3; //组员
string description = 4; //描述
}
//更新投票组
message UpdateGroup {
string groupID = 1; //投票组ID
repeated GroupMember addMembers = 2; //需要增加的组成员
repeated string removeMembers = 3; //删除组成员的地址列表
repeated string addAdmins = 4; //增加管理员
repeated string removeAdmins = 5; //删除管理员
}
// 投票组信息
message GroupInfo {
string ID = 1; //投票组ID
string name = 2; //投票组名称
uint32 memberNum = 3; //组员数量
string creator = 4; //创建者
repeated string admins = 5; //管理员列表
repeated GroupMember members = 6; //成员列表
string description = 7; //描述信息
uint32 voteNum = 8; //投票数量
}
message GroupInfos {
repeated GroupInfo groupList = 1; //投票组信息列表
}
//投票选项
message VoteOption {
string option = 1; //投票选项
uint32 score = 2; //投票得分
}
// 创建投票交易,请求结构
message CreateVote {
string name = 1; //投票名称
string groupID = 2; //投票关联组
repeated string voteOptions = 3; //投票选项列表
int64 beginTimestamp = 4; //投票开始时间戳
int64 endTimestamp = 5; //投票结束时间戳
string description = 6; //描述信息
}
// 创建提交投票交易,请求结构
message CommitVote {
string voteID = 1; //投票ID
uint32 optionIndex = 2; //投票选项数组下标,下标对应投票内容
}
message CommitInfo {
string addr = 1; //提交地址
string txHash = 2; //提交交易哈希
uint32 voteWeight = 3; //投票权重
}
message CloseVote {
string voteID = 1; // 投票ID
}
message UpdateMember {
string name = 1; //用户名称
}
//投票信息
message VoteInfo {
string ID = 1; //投票ID
string name = 2; //投票名称
string creator = 3; //创建者
string groupID = 4; //投票关联的投票组
repeated VoteOption voteOptions = 5; //投票的选项
int64 beginTimestamp = 6; //投票开始时间戳
int64 endTimestamp = 7; //投票结束时间戳
repeated CommitInfo commitInfos = 8; //已投票的提交信息
string description = 9; //描述信息
uint32 status = 10; //状态,1即将开始,2正在进行,3已经结束,4已关闭
}
message VoteInfos {
repeated VoteInfo voteList = 1; //投票信息列表
}
message MemberInfo {
string addr = 1; //地址
string name = 2; //用户名称
repeated string groupIDs = 3; //所属投票组的ID列表
}
message MemberInfos {
repeated MemberInfo memberList = 1; //投票组成员信息列表
}
message ReqStrings {
repeated string items = 1; //请求项数组
}
//列表请求结构
message ReqListItem {
string startItemID = 1; //列表开始的ID,如请求组列表即groupID,不包含在结果中
int32 count = 2; //请求列表项数量, 0表示请求所有
int32 direction = 3; // 0表示根据ID降序,1表示升序,目前ID和区块高度正相关
}
message ReqListVote {
string groupID = 1; //指定所属组ID
ReqListItem listReq = 2; //列表请求
uint32 status = 3; //指定投票状态
}
message ReplyVoteList {
repeated VoteInfo voteList = 1; //投票列表
int64 currentTimestamp = 2; //当前系统时间
}
This diff is collapsed.
package rpc
/*
* 实现json rpc和grpc service接口
* json rpc用Jrpc结构作为接收实例
* grpc使用channelClient结构作为接收实例
*/
package rpc
import (
rpctypes "github.com/33cn/chain33/rpc/types"
)
/*
* rpc相关结构定义和初始化
*/
// 实现grpc的service接口
type channelClient struct {
rpctypes.ChannelClient
}
// Jrpc 实现json rpc调用实例
type Jrpc struct {
cli *channelClient
}
// Grpc grpc
type Grpc struct {
*channelClient
}
// Init init rpc
func Init(name string, s rpctypes.RPCServer) {
cli := &channelClient{}
grpc := &Grpc{channelClient: cli}
cli.Init(name, s, &Jrpc{cli: cli}, grpc)
}
package types
import (
"reflect"
log "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
/*
* 交易相关类型定义
* 交易action通常有对应的log结构,用于交易回执日志记录
* 每一种action和log需要用id数值和name名称加以区分
*/
// action类型id和name,这些常量可以自定义修改
const (
TyUnknowAction = iota + 100
TyCreateGroupAction
TyUpdateGroupAction
TyCreateVoteAction
TyCommitVoteAction
TyCloseVoteAction
TyUpdateMemberAction
NameCreateGroupAction = "CreateGroup"
NameUpdateGroupAction = "UpdateGroup"
NameCreateVoteAction = "CreateVote"
NameCommitVoteAction = "CommitVote"
NameCloseVoteAction = "CloseVote"
NameUpdateMemberAction = "UpdateMember"
)
// log类型id值
const (
TyUnknownLog = iota + 100
TyCreateGroupLog
TyUpdateGroupLog
TyCreateVoteLog
TyCommitVoteLog
TyCloseVoteLog
TyUpdateMemberLog
NameCreateGroupLog = "CreateGroupLog"
NameUpdateGroupLog = "UpdateGroupLog"
NameCreateVoteLog = "CreateVoteLog"
NameCommitVoteLog = "CommitVoteLog"
NameCloseVoteLog = "CloseVoteLog"
NameUpdateMemberLog = "UpdateMemberLog"
)
var (
//VoteX 执行器名称定义
VoteX = "vote"
//定义actionMap
actionMap = map[string]int32{
NameCreateGroupAction: TyCreateGroupAction,
NameUpdateGroupAction: TyUpdateGroupAction,
NameCreateVoteAction: TyCreateVoteAction,
NameCommitVoteAction: TyCommitVoteAction,
NameCloseVoteAction: TyCloseVoteAction,
NameUpdateMemberAction: TyUpdateMemberAction,
}
//定义log的id和具体log类型及名称,填入具体自定义log类型
logMap = map[int64]*types.LogInfo{
TyCreateGroupLog: {Ty: reflect.TypeOf(GroupInfo{}), Name: NameCreateGroupLog},
TyUpdateGroupLog: {Ty: reflect.TypeOf(GroupInfo{}), Name: NameUpdateGroupLog},
TyCreateVoteLog: {Ty: reflect.TypeOf(VoteInfo{}), Name: NameCreateVoteLog},
TyCommitVoteLog: {Ty: reflect.TypeOf(CommitInfo{}), Name: NameCommitVoteLog},
TyCloseVoteLog: {Ty: reflect.TypeOf(VoteInfo{}), Name: NameCloseVoteLog},
TyUpdateMemberLog: {Ty: reflect.TypeOf(MemberInfo{}), Name: NameUpdateMemberLog},
}
tlog = log.New("module", "vote.types")
)
// init defines a register function
func init() {
types.AllowUserExec = append(types.AllowUserExec, []byte(VoteX))
//注册合约启用高度
types.RegFork(VoteX, InitFork)
types.RegExec(VoteX, InitExecutor)
}
// InitFork defines register fork
func InitFork(cfg *types.Chain33Config) {
cfg.RegisterDappFork(VoteX, "Enable", 0)
}
// InitExecutor defines register executor
func InitExecutor(cfg *types.Chain33Config) {
types.RegistorExecutor(VoteX, NewType(cfg))
}
type voteType struct {
types.ExecTypeBase
}
func NewType(cfg *types.Chain33Config) *voteType {
c := &voteType{}
c.SetChild(c)
c.SetConfig(cfg)
return c
}
// GetPayload 获取合约action结构
func (v *voteType) GetPayload() types.Message {
return &VoteAction{}
}
// GeTypeMap 获取合约action的id和name信息
func (v *voteType) GetTypeMap() map[string]int32 {
return actionMap
}
// GetLogMap 获取合约log相关信息
func (v *voteType) GetLogMap() map[int64]*types.LogInfo {
return logMap
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment