Unverified Commit cd952ee4 authored by vipwzw's avatar vipwzw Committed by GitHub

Merge pull request #157 from zzh33cn/guess

Guess
parents ec143947 5e4c5cd9
This diff is collapsed.
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
/*
区块链游戏:竞猜
一、玩法简介:
博彩平台的管理员可以发布竞猜游戏,玩家可以对竞猜游戏进行投注.
截止投注时间达到后,管理员改变游戏状态为停止投注状态,玩家不能再进行投注,等待管理员公布结果.
竞猜内容的结果出来以后,管理员根据竞猜内容的真实结果触发智能合约进行输赢判断及结算。
二、创建及参与游戏
1、游戏管理员创建一局竞猜游戏时,可以设定竞猜内容,选项,赌注类型、大小及上限,截止区块高度等,比如竞猜一场足球比赛的结果,每注10BTY,单次最多可下100注,比赛开始前2小时截止投注(换算成游戏创建后多少个区块)。
2、玩家按规则进行投注,如果已过截止高度或者游戏状态已经被管理员修改为停止投注状态,则不能继续投注。
三、制胜策略
1、竞猜游戏对应的现实世界的结果出来以后,游戏管理员向区块链合约公布胜出的竞猜选项。
2、合约将投注的所有赌注向压注正确选项的玩家进行分配,分配原则根据每个地址实际投注的数额占比进行分配,比如一个地址投注占了正确选项投注总额的1/2,则该地址将获得所有赌注总额的1/2。
3、如果因为现实世界的突发异常导致竞猜不能继续(比如某场足球比赛因为不可抗力取消了,比如自然灾害、政治事件等),则管理员可以终止竞猜,合约将把所有地址的投注返还。
4、在分配奖金前,合约将向开发者地址和平台地址按预定的比率收取手续费,奖励给开发者和平台,剩余奖金在圣者之间分配。
四、游戏过程和状态
1、创建游戏,一个地址可以作为本局竞猜游戏的管理员发起竞猜游戏。
2、管理员创建游戏时指定
竞猜的内容(比如:未来的一场足球比赛)、
选项(比如:A队赢或者B队赢或者打平)、
赌注类型(比如BTY)、
单注大小(比如5BTY)、
单次最多可下注数量(比如100个BTY)、
截止下注高度(从创建游戏的高度算起,比如:区块高度差500000)
游戏超时高度(从创建游戏的高度算起,比如:区块高度差1000000)
可下注的最大数量(比如20000个BTY)
3、截止下注高度之前,玩家可根据游戏要求进行下注,下注金额注入合约地址。如果投注数量已经超过上限,则不允许新的投注。
4、截止下注高度达到后,玩家不可再下注。或者管理员将游戏状态设定为停止投注状态。
5、现实世界的竞猜结果出现后,管理员在游戏超时时间之前公布游戏结果。
6、(1)合约先对所有赌注收取一定比例的佣金,比如5‰给开发者地址,5‰给平台地址,(2)合约根据管理员输入的正确结果,对每个投注地址进行输赢判断,并将提取佣金后的剩余所有赌注对所有竞猜正确的地址按各自的投注额占比进行比例分配(比如A选项正确,所有选A的赌注共10000个BTY,某个地址向A下注100BTY,则该地址分得1/100)。
7、如果因为现实世界的突发异常导致竞猜不能继续(比如某场足球比赛因为不可抗力取消了),则管理员可以终止竞猜,合约将把所有地址的投注返还。
8、如果游戏超时,管理员仍未公布结果,则任何地址都可以触发合约异常终止竞猜,合约中的投注返还给原投注地址。
8、游戏状态:
start(管理员)->bet(玩家)->stopbet(管理员)->publish(管理员)
start(管理员)->bet(玩家)->stopbet(管理员)->abort(管理员)
start(管理员)->bet(玩家)->abort(管理员)
start(管理员)->abort(管理员)
start(管理员)->bet(玩家)->stopbet(管理员)->timeout->abort(任何人)
start(管理员)->bet(玩家)->timeout->abort(任何人)
start(管理员)->timeout->abort(任何人)
start(管理员)->stopbet(管理员)->publish(管理员)
start(管理员)->bet(玩家)->publish(管理员)
start(管理员)->publish(管理员)
说明:这里的管理员不是特殊地址,而是谁创建本局竞猜游戏,谁就是本局竞猜游戏的管理员。
*/
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"github.com/33cn/chain33/types"
gty "github.com/33cn/plugin/plugin/dapp/guess/types"
)
//Exec_Start Guess执行器创建游戏
func (c *Guess) Exec_Start(payload *gty.GuessGameStart, tx *types.Transaction, index int) (*types.Receipt, error) {
action := NewAction(c, tx, index)
return action.GameStart(payload)
}
//Exec_Bet Guess执行器参与游戏
func (c *Guess) Exec_Bet(payload *gty.GuessGameBet, tx *types.Transaction, index int) (*types.Receipt, error) {
action := NewAction(c, tx, index)
return action.GameBet(payload)
}
//Exec_StopBet Guess执行器停止游戏下注
func (c *Guess) Exec_StopBet(payload *gty.GuessGameStopBet, tx *types.Transaction, index int) (*types.Receipt, error) {
action := NewAction(c, tx, index)
return action.GameStopBet(payload)
}
//Exec_Publish Guess执行器公布游戏结果
func (c *Guess) Exec_Publish(payload *gty.GuessGamePublish, tx *types.Transaction, index int) (*types.Receipt, error) {
action := NewAction(c, tx, index)
return action.GamePublish(payload)
}
//Exec_Abort Guess执行器撤销未结束游戏
func (c *Guess) Exec_Abort(payload *gty.GuessGameAbort, tx *types.Transaction, index int) (*types.Receipt, error) {
action := NewAction(c, tx, index)
return action.GameAbort(payload)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"fmt"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
gty "github.com/33cn/plugin/plugin/dapp/guess/types"
)
func (g *Guess) rollbackGame(game *gty.GuessGame, log *gty.ReceiptGuessGame) {
if game == nil || log == nil {
return
}
//如果状态发生了变化,则需要将游戏状态恢复到前一状态
if log.StatusChange {
game.Status = log.PreStatus
game.Index = log.PreIndex
//玩家信息中的index回滚
for i := 0; i < len(game.Plays); i++ {
player := game.Plays[i]
player.Bet.Index = player.Bet.PreIndex
}
}
//如果下注了,则需要把下注回滚
if log.Bet {
//统计信息回滚
game.BetStat.TotalBetTimes--
game.BetStat.TotalBetsNumber -= log.BetsNumber
for i := 0; i < len(game.BetStat.Items); i++ {
item := game.BetStat.Items[i]
if item.Option == log.Option {
item.BetsTimes--
item.BetsNumber -= log.BetsNumber
break
}
}
//玩家下注信息回滚
for i := 0; i < len(game.Plays); i++ {
player := game.Plays[i]
if player.Addr == log.Addr && player.Bet.Index == log.Index {
game.Plays = append(game.Plays[:i], game.Plays[i+1:]...)
break
}
}
}
}
func (g *Guess) rollbackIndex(log *gty.ReceiptGuessGame) (kvs []*types.KeyValue) {
userTable := gty.NewGuessUserTable(g.GetLocalDB())
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
tablejoin, err := table.NewJoinTable(userTable, gameTable, []string{"addr#status"})
if err != nil {
return nil
}
if log.Status == gty.GuessGameStatusStart {
//新创建游戏回滚,game表删除记录
err = gameTable.Del([]byte(fmt.Sprintf("%018d", log.StartIndex)))
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
return kvs
} else if log.Status == gty.GuessGameStatusBet {
//下注阶段,需要更新游戏信息,回滚下注信息
game := log.Game
log.Game = nil
//先回滚游戏信息,再进行更新
g.rollbackGame(game, log)
err = tablejoin.MustGetTable("game").Replace(game)
if err != nil {
return nil
}
err = tablejoin.MustGetTable("user").Del([]byte(fmt.Sprintf("%018d", log.Index)))
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
} else if log.StatusChange {
//如果是其他状态下仅发生了状态变化,则需要恢复游戏状态,并更新游戏记录。
game := log.Game
log.Game = nil
//先回滚游戏信息,再进行更新
g.rollbackGame(game, log)
err = tablejoin.MustGetTable("game").Replace(game)
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
}
return kvs
}
func (g *Guess) execDelLocal(receipt *types.ReceiptData) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
if receipt.GetTy() != types.ExecOk {
return dbSet, nil
}
for _, log := range receipt.Logs {
switch log.GetTy() {
case gty.TyLogGuessGameStart, gty.TyLogGuessGameBet, gty.TyLogGuessGameStopBet, gty.TyLogGuessGameAbort, gty.TyLogGuessGamePublish, gty.TyLogGuessGameTimeout:
receiptGame := &gty.ReceiptGuessGame{}
if err := types.Decode(log.Log, receiptGame); err != nil {
return nil, err
}
kv := g.rollbackIndex(receiptGame)
dbSet.KV = append(dbSet.KV, kv...)
}
}
return dbSet, nil
}
//ExecDelLocal_Start Guess执行器Start交易撤销
func (g *Guess) ExecDelLocal_Start(payload *gty.GuessGameStart, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecDelLocal_Bet Guess执行器Bet交易撤销
func (g *Guess) ExecDelLocal_Bet(payload *gty.GuessGameBet, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecDelLocal_Publish Guess执行器Publish交易撤销
func (g *Guess) ExecDelLocal_Publish(payload *gty.GuessGamePublish, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecDelLocal_Abort Guess执行器Abort交易撤销
func (g *Guess) ExecDelLocal_Abort(payload *gty.GuessGameAbort, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
gty "github.com/33cn/plugin/plugin/dapp/guess/types"
)
func (g *Guess) getUserBet(log *gty.ReceiptGuessGame) (userBet *gty.UserBet) {
userBet = &gty.UserBet{}
userBet.StartIndex = log.StartIndex
userBet.Index = log.Index
userBet.GameID = log.GameID
userBet.Addr = log.Addr
if log.Bet {
userBet.Option = log.Option
userBet.BetsNumber = log.BetsNumber
}
return userBet
}
func (g *Guess) updateIndex(log *gty.ReceiptGuessGame) (kvs []*types.KeyValue) {
userTable := gty.NewGuessUserTable(g.GetLocalDB())
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
tablejoin, err := table.NewJoinTable(userTable, gameTable, []string{"addr#status"})
if err != nil {
return nil
}
if log.Status == gty.GuessGameStatusStart {
//新创建游戏,game表新增记录
game := log.Game
log.Game = nil
err = tablejoin.MustGetTable("game").Replace(game)
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
return kvs
} else if log.Status == gty.GuessGameStatusBet {
//用户下注,game表发生更新(game中下注信息有更新),user表新增下注记录
game := log.Game
log.Game = nil
userBet := g.getUserBet(log)
err = tablejoin.MustGetTable("game").Replace(game)
if err != nil {
return nil
}
err = tablejoin.MustGetTable("user").Replace(userBet)
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
return kvs
} else if log.StatusChange {
//其他状态,游戏状态变化,只需要更新game表
game := log.Game
log.Game = nil
err = tablejoin.MustGetTable("game").Replace(game)
if err != nil {
return nil
}
kvs, _ = tablejoin.Save()
return kvs
}
return kvs
}
func (g *Guess) execLocal(receipt *types.ReceiptData) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
if receipt.GetTy() != types.ExecOk {
return dbSet, nil
}
for _, item := range receipt.Logs {
if item.Ty >= gty.TyLogGuessGameStart && item.Ty <= gty.TyLogGuessGameTimeout {
var gameLog gty.ReceiptGuessGame
err := types.Decode(item.Log, &gameLog)
if err != nil {
return nil, err
}
kvs := g.updateIndex(&gameLog)
dbSet.KV = append(dbSet.KV, kvs...)
}
}
return dbSet, nil
}
//ExecLocal_Start method
func (g *Guess) ExecLocal_Start(payload *gty.GuessGameStart, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecLocal_Bet method
func (g *Guess) ExecLocal_Bet(payload *gty.GuessGameBet, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecLocal_StopBet method
func (g *Guess) ExecLocal_StopBet(payload *gty.GuessGameStopBet, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecLocal_Publish method
func (g *Guess) ExecLocal_Publish(payload *gty.GuessGamePublish, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
//ExecLocal_Abort method
func (g *Guess) ExecLocal_Abort(payload *gty.GuessGameAbort, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return g.execLocal(receiptData)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
log "github.com/33cn/chain33/common/log/log15"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
gty "github.com/33cn/plugin/plugin/dapp/guess/types"
)
var logger = log.New("module", "execs.guess")
var driverName = gty.GuessX
func init() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&Guess{}))
}
type subConfig struct {
ParaRemoteGrpcClient string `json:"paraRemoteGrpcClient"`
}
var cfg subConfig
// Init Guess
func Init(name string, sub []byte) {
driverName := GetName()
if name != driverName {
panic("system dapp can't be rename")
}
if sub != nil {
types.MustDecode(sub, &cfg)
}
drivers.Register(driverName, newGuessGame, types.GetDappFork(driverName, "Enable"))
}
//Guess 执行器,用于竞猜合约的具体执行
type Guess struct {
drivers.DriverBase
}
func newGuessGame() drivers.Driver {
t := &Guess{}
t.SetChild(t)
t.SetExecutorType(types.LoadExecutorType(driverName))
return t
}
//GetName 获取Guess执行器的名称
func GetName() string {
return newGuessGame().GetName()
}
//GetDriverName 获取Guess执行器的名称
func (g *Guess) GetDriverName() string {
return gty.GuessX
}
/*
// GetPayloadValue GuessAction
func (g *Guess) GetPayloadValue() types.Message {
return &pkt.GuessGameAction{}
}*/
// CheckReceiptExecOk return true to check if receipt ty is ok
func (g *Guess) CheckReceiptExecOk() bool {
return true
}
This diff is collapsed.
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"fmt"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
gty "github.com/33cn/plugin/plugin/dapp/guess/types"
)
//Query_QueryGamesByIDs method
func (g *Guess) Query_QueryGamesByIDs(in *gty.QueryGuessGameInfos) (types.Message, error) {
return queryGameInfos(g.GetLocalDB(), in)
}
//Query_QueryGameByID method
func (g *Guess) Query_QueryGameByID(in *gty.QueryGuessGameInfo) (types.Message, error) {
game, err := queryGameInfo(g.GetLocalDB(), []byte(in.GetGameID()))
if err != nil {
return nil, err
}
return &gty.ReplyGuessGameInfo{Game: game}, nil
}
//Query_QueryGamesByAddr method
func (g *Guess) Query_QueryGamesByAddr(in *gty.QueryGuessGameInfo) (types.Message, error) {
gameTable := gty.NewGuessUserTable(g.GetLocalDB())
query := gameTable.GetQuery(g.GetLocalDB())
return queryUserTableData(query, "addr", []byte(in.Addr), []byte(in.PrimaryKey))
}
//Query_QueryGamesByStatus method
func (g *Guess) Query_QueryGamesByStatus(in *gty.QueryGuessGameInfo) (types.Message, error) {
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
query := gameTable.GetQuery(g.GetLocalDB())
return queryGameTableData(query, "status", []byte(fmt.Sprintf("%2d", in.Status)), []byte(in.PrimaryKey))
}
//Query_QueryGamesByAdminAddr method
func (g *Guess) Query_QueryGamesByAdminAddr(in *gty.QueryGuessGameInfo) (types.Message, error) {
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
query := gameTable.GetQuery(g.GetLocalDB())
prefix := []byte(in.AdminAddr)
return queryGameTableData(query, "admin", prefix, []byte(in.PrimaryKey))
}
//Query_QueryGamesByAddrStatus method
func (g *Guess) Query_QueryGamesByAddrStatus(in *gty.QueryGuessGameInfo) (types.Message, error) {
userTable := gty.NewGuessUserTable(g.GetLocalDB())
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
tableJoin, err := table.NewJoinTable(userTable, gameTable, []string{"addr#status"})
if err != nil {
return nil, err
}
prefix := table.JoinKey([]byte(in.Addr), []byte(fmt.Sprintf("%2d", in.Status)))
return queryJoinTableData(tableJoin, "addr#status", prefix, []byte(in.PrimaryKey))
}
//Query_QueryGamesByAdminStatus method
func (g *Guess) Query_QueryGamesByAdminStatus(in *gty.QueryGuessGameInfo) (types.Message, error) {
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
query := gameTable.GetQuery(g.GetLocalDB())
prefix := []byte(fmt.Sprintf("%s:%2d", in.AdminAddr, in.Status))
return queryGameTableData(query, "admin_status", prefix, []byte(in.PrimaryKey))
}
//Query_QueryGamesByCategoryStatus method
func (g *Guess) Query_QueryGamesByCategoryStatus(in *gty.QueryGuessGameInfo) (types.Message, error) {
gameTable := gty.NewGuessGameTable(g.GetLocalDB())
query := gameTable.GetQuery(g.GetLocalDB())
prefix := []byte(fmt.Sprintf("%s:%2d", in.Category, in.Status))
return queryGameTableData(query, "category_status", prefix, []byte(in.PrimaryKey))
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package guess
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/guess/commands"
"github.com/33cn/plugin/plugin/dapp/guess/executor"
"github.com/33cn/plugin/plugin/dapp/guess/types"
)
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: types.GuessX,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: commands.GuessCmd,
})
}
all:
sh ./create_protobuf.sh
#!/bin/sh
protoc --go_out=plugins=grpc:../types ./*.proto --proto_path=. --proto_path="../../../../vendor/github.com/33cn/chain33/types/proto/"
syntax = "proto3";
import "transaction.proto";
package types;
//GuessGame 竞猜游戏详情
message GuessGame {
string gameID = 1; //游戏ID
int32 status = 2; //游戏的状态:创建->投注->截止投注->开奖
int32 preStatus = 3;
int64 startTime = 4; //创建游戏的时间
int64 startHeight = 5; //创建游戏的时间
string startTxHash = 6; //创建游戏的交易hash
int64 startIndex = 7; //创建游戏的交易index
string topic = 8; //主题
string category = 9; //分类
string options = 10; //选项
int64 maxBetHeight = 11; //截止下注的块高
int64 maxBetsOneTime = 12; //单次可以下多少注,默认100
int64 maxBetsNumber = 13; //最多可以下多少注
int64 devFeeFactor = 14; //开发者抽成比例
string devFeeAddr = 15; //开发者地址
int64 platFeeFactor = 16; //平台抽成比例
string platFeeAddr = 17; //平台地址
int64 expireHeight = 18; //游戏过期区块高度
string adminAddr = 19; //游戏创建者地址,只有该地址可以开奖
int64 betsNumber = 20; //已下注数,如果数量达到maxBetsNumber,则不允许再下注
repeated GuessPlayer plays = 21; //参与游戏下注的玩家投注信息
string result = 22; //公布的中奖结果
GuessBetStat betStat = 23;
int64 index = 24;
int64 preIndex = 25;
bool drivenByAdmin = 26;
}
//GuessPlayer 竞猜玩家信息
message GuessPlayer {
string addr = 1;
GuessBet bet = 2;
}
//GuessBet 竞猜下注信息
message GuessBet {
string option = 1;
int64 betsNumber = 2;
bool isWinner = 3;
int64 profit = 4;
int64 index = 5;
int64 preIndex = 6;
}
//GuessBetStat 竞猜下注统计信息
message GuessBetStat {
int64 totalBetTimes = 1;
int64 totalBetsNumber = 2;
repeated GuessBetStatItem items = 3;
}
//GuessBetStat 竞猜下注子选项统计信息
message GuessBetStatItem {
string option = 1;
int64 betsNumber = 2;
int64 betsTimes = 3;
}
//GuessGameAction 竞猜游戏动作
message GuessGameAction {
oneof value {
GuessGameStart start = 1;
GuessGameBet bet = 2;
GuessGameStopBet stopBet = 3;
GuessGameAbort abort = 4;
GuessGamePublish publish = 5;
GuessGameQuery query = 6;
}
int32 ty = 7;
}
//GuessGameStart 游戏创建
message GuessGameStart{
string topic = 1;
string options = 2;
string category = 3;
int64 maxBetHeight = 4;
int64 maxBetsOneTime= 5;
int64 maxBetsNumber = 6;
int64 devFeeFactor = 7; //开发者抽成比例
string devFeeAddr = 8; //开发者地址
int64 platFeeFactor = 9; //平台抽成比例
string platFeeAddr = 10; //平台地址
int64 expireHeight = 11;
bool drivenByAdmin = 12;
}
//GuessGameBet 参与游戏下注
message GuessGameBet{
string gameID = 1;
string option = 2;
int64 betsNum = 3;
}
//GuessGameStopBet 游戏停止下注
message GuessGameStopBet{
string gameID = 1;
}
//GuessGameAbort 游戏异常终止,退还下注
message GuessGameAbort{
string gameID = 1;
}
//GuessGamePublish 游戏结果揭晓
message GuessGamePublish{
string gameID = 1;
string result = 2;
}
//GuessGameQuery 查询游戏结果
message GuessGameQuery{
string gameID = 1;
uint32 ty = 2;
}
//QueryGuessGameInfo 游戏信息查询消息
message QueryGuessGameInfo {
string gameID = 1;
string addr = 2;
int32 status = 3;
int64 index = 4;
string adminAddr = 5;
string category = 6;
string primaryKey = 7;
}
//ReplyGuessGameInfo 游戏信息查询响应消息
message ReplyGuessGameInfo {
GuessGame game = 1;
}
//QueryGuessGameInfos 游戏信息列表查询消息
message QueryGuessGameInfos {
repeated string gameIDs = 1;
}
//ReplyGuessGameInfos 游戏信息列表查询响应消息
message ReplyGuessGameInfos {
repeated GuessGame games = 1;
}
//ReceiptGuessGame 竞猜游戏收据信息
message ReceiptGuessGame {
int64 startIndex = 1;
string gameID = 2;
int32 preStatus = 3;
int32 status = 4;
string addr = 5;
string adminAddr = 6;
int64 preIndex = 7;
int64 index = 8;
string category = 9;
bool statusChange = 10;
bool bet = 11;
string option = 12;
int64 betsNumber = 13;
GuessGame game = 14;
}
//UserBet 用户下注信息
message UserBet {
int64 startIndex = 1;
int64 index = 2;
string gameID = 3;
string addr = 4;
string option = 5;
int64 betsNumber = 6;
}
//GuessStartTxReq 构造start交易的请求
message GuessStartTxReq {
string topic = 1;
string options = 2;
string category = 3;
int64 maxHeight = 4;
int64 maxBetHeight = 5;
int64 maxBetsOneTime = 6;
int64 maxBetsNumber = 7;
int64 devFeeFactor = 8;
string devFeeAddr = 9;
int64 platFeeFactor = 10;
string platFeeAddr = 11;
int64 expireHeight = 12;
int64 fee = 13;
}
//GuessBetTxReq 构造bet交易的请求
message GuessBetTxReq {
string gameID = 1;
string option = 2;
int64 bets = 3;
int64 fee = 4;
}
//GuessStopBetTxReq 构造stopBet交易的请求
message GuessStopBetTxReq {
string gameID = 1;
int64 fee = 2;
}
//GuessAbortTxReq 构造abort交易的请求
message GuessAbortTxReq {
string gameID = 1;
int64 fee = 2;
}
//GuessPublishTxReq 构造publish交易的请求
message GuessPublishTxReq {
string gameID = 1;
string result = 2;
int64 fee = 3;
}
// GuessGameRecord game信息查询记录
message GuessGameRecord {
string gameID = 1;
int64 startIndex = 2;
}
// GuessGameRecords game信息查询记录集
message GuessGameRecords {
repeated GuessGameRecord records = 1;
string primaryKey = 2;
}
// service guess 为guess 对外提供服务的接口
service guess {
//游戏开始
rpc GuessStart(GuessGameStart) returns (UnsignTx) {}
//游戏下注
rpc GuessBet(GuessGameBet) returns (UnsignTx) {}
//游戏终止下注
rpc GuessStopBet(GuessGameStopBet) returns (UnsignTx) {}
//游戏异常终止
rpc GuessAbort(GuessGameAbort) returns (UnsignTx) {}
//游戏结束
rpc GuessPublish(GuessGamePublish) returns (UnsignTx) {}
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rpc_test
import (
"fmt"
"testing"
commonlog "github.com/33cn/chain33/common/log"
"github.com/33cn/chain33/rpc/jsonclient"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util/testnode"
pty "github.com/33cn/plugin/plugin/dapp/guess/types"
"github.com/stretchr/testify/assert"
_ "github.com/33cn/chain33/system"
_ "github.com/33cn/plugin/plugin"
)
func init() {
commonlog.SetLogLevel("error")
}
func TestJRPCChannel(t *testing.T) {
// 启动RPCmocker
mocker := testnode.New("--notset--", nil)
defer func() {
mocker.Close()
}()
mocker.Listen()
jrpcClient := mocker.GetJSONC()
assert.NotNil(t, jrpcClient)
testCases := []struct {
fn func(*testing.T, *jsonclient.JSONClient) error
}{
{fn: testStartRawTxCmd},
{fn: testBetRawTxCmd},
{fn: testStopBetRawTxCmd},
{fn: testPublishRawTxCmd},
{fn: testAbortRawTxCmd},
}
for _, testCase := range testCases {
err := testCase.fn(t, jrpcClient)
assert.Nil(t, err)
}
testCases = []struct {
fn func(*testing.T, *jsonclient.JSONClient) error
}{
{fn: testQueryGameByID},
{fn: testQueryGamesByAddr},
{fn: testQueryGamesByStatus},
{fn: testQueryGamesByAdminAddr},
{fn: testQueryGamesByAddrStatus},
{fn: testQueryGamesByAdminStatus},
{fn: testQueryGamesByCategoryStatus},
}
for index, testCase := range testCases {
err := testCase.fn(t, jrpcClient)
assert.Equal(t, err, types.ErrNotFound, fmt.Sprint(index))
}
testCases = []struct {
fn func(*testing.T, *jsonclient.JSONClient) error
}{
{fn: testQueryGamesByIDs},
}
for index, testCase := range testCases {
err := testCase.fn(t, jrpcClient)
assert.Equal(t, err, nil, fmt.Sprint(index))
}
}
func testStartRawTxCmd(t *testing.T, jrpc *jsonclient.JSONClient) error {
payload := &pty.GuessGameStart{Topic: "WorldCup Final", Options: "A:France;B:Claodia", Category: "football", MaxBetsOneTime: 100e8, MaxBetsNumber: 1000e8, DevFeeFactor: 5, DevFeeAddr: "1D6RFZNp2rh6QdbcZ1d7RWuBUz61We6SD7", PlatFeeFactor: 5, PlatFeeAddr: "1PHtChNt3UcfssR7v7trKSk3WJtAWjKjjX"}
params := &rpctypes.CreateTxIn{
Execer: types.ExecName(pty.GuessX),
ActionName: pty.CreateStartTx,
Payload: types.MustPBToJSON(payload),
}
var res string
return jrpc.Call("Chain33.CreateTransaction", params, &res)
}
func testBetRawTxCmd(t *testing.T, jrpc *jsonclient.JSONClient) error {
payload := &pty.GuessGameBet{GameID: "0x76dae82fcbe554d4b8df5ed1460d71dcac86a50864649a0df43e0c50b245f004", Option: "A", BetsNum: 5e8}
params := &rpctypes.CreateTxIn{
Execer: types.ExecName(pty.GuessX),
ActionName: pty.CreateBetTx,
Payload: types.MustPBToJSON(payload),
}
var res string
return jrpc.Call("Chain33.CreateTransaction", params, &res)
}
func testStopBetRawTxCmd(t *testing.T, jrpc *jsonclient.JSONClient) error {
payload := &pty.GuessGameStopBet{GameID: "0x76dae82fcbe554d4b8df5ed1460d71dcac86a50864649a0df43e0c50b245f004"}
params := &rpctypes.CreateTxIn{
Execer: types.ExecName(pty.GuessX),
ActionName: pty.CreateStopBetTx,
Payload: types.MustPBToJSON(payload),
}
var res string
return jrpc.Call("Chain33.CreateTransaction", params, &res)
}
func testPublishRawTxCmd(t *testing.T, jrpc *jsonclient.JSONClient) error {
payload := &pty.GuessGamePublish{GameID: "0x76dae82fcbe554d4b8df5ed1460d71dcac86a50864649a0df43e0c50b245f004", Result: "A"}
params := &rpctypes.CreateTxIn{
Execer: types.ExecName(pty.GuessX),
ActionName: pty.CreatePublishTx,
Payload: types.MustPBToJSON(payload),
}
var res string
return jrpc.Call("Chain33.CreateTransaction", params, &res)
}
func testAbortRawTxCmd(t *testing.T, jrpc *jsonclient.JSONClient) error {
payload := &pty.GuessGameAbort{GameID: "0x76dae82fcbe554d4b8df5ed1460d71dcac86a50864649a0df43e0c50b245f004"}
params := &rpctypes.CreateTxIn{
Execer: types.ExecName(pty.GuessX),
ActionName: pty.CreateAbortTx,
Payload: types.MustPBToJSON(payload),
}
var res string
return jrpc.Call("Chain33.CreateTransaction", params, &res)
}
func testQueryGameByID(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByID
params.Payload = types.MustPBToJSON(req)
rep = &pty.ReplyGuessGameInfo{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByAddr(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByAddr
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByIDs(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfos{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGamesByIDs
params.Payload = types.MustPBToJSON(req)
rep = &pty.ReplyGuessGameInfos{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByStatus(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByStatus
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByAdminAddr(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByAdminAddr
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByAddrStatus(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByAddrStatus
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByAdminStatus(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByAdminStatus
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
func testQueryGamesByCategoryStatus(t *testing.T, jrpc *jsonclient.JSONClient) error {
var rep interface{}
var params rpctypes.Query4Jrpc
req := &pty.QueryGuessGameInfo{}
params.Execer = pty.GuessX
params.FuncName = pty.FuncNameQueryGameByCategoryStatus
params.Payload = types.MustPBToJSON(req)
rep = &pty.GuessGameRecords{}
return jrpc.Call("Chain33.Query", params, rep)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
//game action ty
const (
PBGameActionStart = iota + 1
PBGameActionContinue
PBGameActionQuit
PBGameActionQuery
GuessGameActionStart = iota + 1
GuessGameActionBet
GuessGameActionStopBet
GuessGameActionAbort
GuessGameActionPublish
GuessGameActionQuery
GuessGameStatusStart = iota + 1
GuessGameStatusBet
GuessGameStatusStopBet
GuessGameStatusAbort
GuessGameStatusPublish
GuessGameStatusTimeOut
)
//game log ty
const (
TyLogGuessGameStart = 901
TyLogGuessGameBet = 902
TyLogGuessGameStopBet = 903
TyLogGuessGameAbort = 904
TyLogGuessGamePublish = 905
TyLogGuessGameTimeout = 906
)
//包的名字可以通过配置文件来配置
//建议用github的组织名称,或者用户名字开头, 再加上自己的插件的名字
//如果发生重名,可以通过配置文件修改这些名字
var (
GuessX = "guess"
ExecerGuess = []byte(GuessX)
)
const (
//FuncNameQueryGamesByIDs func name
FuncNameQueryGamesByIDs = "QueryGamesByIDs"
//FuncNameQueryGameByID func name
FuncNameQueryGameByID = "QueryGameByID"
//FuncNameQueryGameByAddr func name
FuncNameQueryGameByAddr = "QueryGamesByAddr"
//FuncNameQueryGameByStatus func name
FuncNameQueryGameByStatus = "QueryGamesByStatus"
//FuncNameQueryGameByAdminAddr func name
FuncNameQueryGameByAdminAddr = "QueryGamesByAdminAddr"
//FuncNameQueryGameByAddrStatus func name
FuncNameQueryGameByAddrStatus = "QueryGamesByAddrStatus"
//FuncNameQueryGameByAdminStatus func name
FuncNameQueryGameByAdminStatus = "QueryGamesByAdminStatus"
//FuncNameQueryGameByCategoryStatus func name
FuncNameQueryGameByCategoryStatus = "QueryGamesByCategoryStatus"
//CreateStartTx 创建开始交易
CreateStartTx = "Start"
//CreateBetTx 创建下注交易
CreateBetTx = "Bet"
//CreateStopBetTx 创建停止下注交易
CreateStopBetTx = "StopBet"
//CreatePublishTx 创建公布结果交易
CreatePublishTx = "Publish"
//CreateAbortTx 创建撤销游戏交易
CreateAbortTx = "Abort"
)
const (
//DevShareAddr default value
DevShareAddr = "1D6RFZNp2rh6QdbcZ1d7RWuBUz61We6SD7"
//PlatformShareAddr default value
PlatformShareAddr = "1PHtChNt3UcfssR7v7trKSk3WJtAWjKjjX"
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import "errors"
// Errors for lottery
var (
ErrNoPrivilege = errors.New("ErrNoPrivilege")
ErrGuessStatus = errors.New("ErrGuessStatus")
ErrOverBetsLimit = errors.New("ErrOverBetsLimit")
ErrParamStatusInvalid = errors.New("ErrParamStatusInvalid")
ErrParamAddressMustnotEmpty = errors.New("ErrParamAddressMustnotEmpty")
ErrGameNotExist = errors.New("ErrGameNotExist")
ErrSaveTable = errors.New("ErrSaveTable")
)
This diff is collapsed.
package types
import (
"fmt"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
)
/*
table struct
data: guess
index: addr,status,addr_status,admin,admin_status,category_status
*/
var opt_guess_user = &table.Option{
Prefix: "LODB_guess",
Name: "user",
Primary: "index",
Index: []string{"addr", "startindex"},
}
//NewGuessUserTable 新建表
func NewGuessUserTable(kvdb db.KV) *table.Table {
rowmeta := NewGuessUserRow()
table, err := table.NewTable(rowmeta, kvdb, opt_guess_user)
if err != nil {
panic(err)
}
return table
}
//GuessUserRow table meta 结构
type GuessUserRow struct {
*UserBet
}
//NewGuessUserRow 新建一个meta 结构
func NewGuessUserRow() *GuessUserRow {
return &GuessUserRow{UserBet: &UserBet{}}
}
//CreateRow 新建数据行(注意index 数据一定也要保存到数据中,不能就保存eventid)
func (tx *GuessUserRow) CreateRow() *table.Row {
return &table.Row{Data: &UserBet{}}
}
//SetPayload 设置数据
func (tx *GuessUserRow) SetPayload(data types.Message) error {
if txdata, ok := data.(*UserBet); ok {
tx.UserBet = txdata
return nil
}
return types.ErrTypeAsset
}
//Get 按照indexName 查询 indexValue
func (tx *GuessUserRow) Get(key string) ([]byte, error) {
if key == "index" {
return []byte(fmt.Sprintf("%018d", tx.Index)), nil
} else if key == "addr" {
return []byte(tx.Addr), nil
} else if key == "startindex" {
return []byte(fmt.Sprintf("%018d", tx.StartIndex)), nil
}
return nil, types.ErrNotFound
}
var opt_guess_game = &table.Option{
Prefix: "LODB_guess",
Name: "game",
Primary: "startindex",
Index: []string{"gameid", "status", "admin", "admin_status", "category_status"},
}
//NewGuessGameTable 新建表
func NewGuessGameTable(kvdb db.KV) *table.Table {
rowmeta := NewGuessGameRow()
table, err := table.NewTable(rowmeta, kvdb, opt_guess_game)
if err != nil {
panic(err)
}
return table
}
//GuessGameRow table meta 结构
type GuessGameRow struct {
*GuessGame
}
//NewGuessGameRow 新建一个meta 结构
func NewGuessGameRow() *GuessGameRow {
return &GuessGameRow{GuessGame: &GuessGame{}}
}
//CreateRow 新建数据行(注意index 数据一定也要保存到数据中,不能就保存eventid)
func (tx *GuessGameRow) CreateRow() *table.Row {
return &table.Row{Data: &GuessGame{}}
}
//SetPayload 设置数据
func (tx *GuessGameRow) SetPayload(data types.Message) error {
if txdata, ok := data.(*GuessGame); ok {
tx.GuessGame = txdata
return nil
}
return types.ErrTypeAsset
}
//Get 按照indexName 查询 indexValue
func (tx *GuessGameRow) Get(key string) ([]byte, error) {
if key == "startindex" {
return []byte(fmt.Sprintf("%018d", tx.StartIndex)), nil
} else if key == "gameid" {
return []byte(tx.GameID), nil
} else if key == "status" {
return []byte(fmt.Sprintf("%2d", tx.Status)), nil
} else if key == "admin" {
return []byte(tx.AdminAddr), nil
} else if key == "admin_status" {
return []byte(fmt.Sprintf("%s:%2d", tx.AdminAddr, tx.Status)), nil
} else if key == "category_status" {
return []byte(fmt.Sprintf("%s:%2d", tx.Category, tx.Status)), nil
}
return nil, types.ErrNotFound
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import (
"reflect"
"github.com/33cn/chain33/types"
)
func init() {
// init executor type
types.RegistorExecutor(GuessX, NewType())
types.AllowUserExec = append(types.AllowUserExec, ExecerGuess)
types.RegisterDappFork(GuessX, "Enable", 0)
}
// GuessType struct
type GuessType struct {
types.ExecTypeBase
}
// NewType method
func NewType() *GuessType {
c := &GuessType{}
c.SetChild(c)
return c
}
// GetPayload method
func (t *GuessType) GetPayload() types.Message {
return &GuessGameAction{}
}
// GetTypeMap method
func (t *GuessType) GetTypeMap() map[string]int32 {
return map[string]int32{
"Start": GuessGameActionStart,
"Bet": GuessGameActionBet,
"StopBet": GuessGameActionStopBet,
"Abort": GuessGameActionAbort,
"Publish": GuessGameActionPublish,
"Query": GuessGameActionQuery,
}
}
// GetLogMap method
func (t *GuessType) GetLogMap() map[int64]*types.LogInfo {
return map[int64]*types.LogInfo{
TyLogGuessGameStart: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGameStart"},
TyLogGuessGameBet: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGameBet"},
TyLogGuessGameStopBet: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGameStopBet"},
TyLogGuessGameAbort: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGameAbort"},
TyLogGuessGamePublish: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGamePublish"},
TyLogGuessGameTimeout: {Ty: reflect.TypeOf(ReceiptGuessGame{}), Name: "TyLogGuessGameTimeout"},
}
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
_ "github.com/33cn/plugin/plugin/dapp/echo" //auto gen _ "github.com/33cn/plugin/plugin/dapp/echo" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/evm" //auto gen _ "github.com/33cn/plugin/plugin/dapp/evm" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/game" //auto gen _ "github.com/33cn/plugin/plugin/dapp/game" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/guess" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/hashlock" //auto gen _ "github.com/33cn/plugin/plugin/dapp/hashlock" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/js" //auto gen _ "github.com/33cn/plugin/plugin/dapp/js" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen _ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen
......
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