Commit 76e3637e authored by 袁兴强's avatar 袁兴强

Merge remote-tracking branch 'upstream/master' into update_chain33

parents 743fb9a9 e87e1e92
...@@ -337,8 +337,8 @@ Enable=0 ...@@ -337,8 +337,8 @@ Enable=0
Enable=0 Enable=0
ForkStorageLocalDB=0 ForkStorageLocalDB=0
[fork.sub.qbftNode]
Enable=0
[fork.sub.multisig] [fork.sub.multisig]
Enable=0 Enable=0
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
_ "github.com/33cn/plugin/plugin/consensus/dpos" //auto gen _ "github.com/33cn/plugin/plugin/consensus/dpos" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/para" //auto gen _ "github.com/33cn/plugin/plugin/consensus/para" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/pbft" //auto gen _ "github.com/33cn/plugin/plugin/consensus/pbft" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/qbft" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/raft" //auto gen _ "github.com/33cn/plugin/plugin/consensus/raft" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/tendermint" //auto gen _ "github.com/33cn/plugin/plugin/consensus/tendermint" //auto gen
_ "github.com/33cn/plugin/plugin/consensus/ticket" //auto gen _ "github.com/33cn/plugin/plugin/consensus/ticket" //auto gen
......
Title="local"
[log]
# 日志级别,支持debug(dbug)/info/warn/error(eror)/crit
loglevel = "debug"
logConsoleLevel = "info"
# 日志文件名,可带目录,所有生成的日志文件都放到此目录下
logFile = "logs/chain33.log"
# 单个日志文件的最大值(单位:兆)
maxFileSize = 300
# 最多保存的历史日志文件个数
maxBackups = 100
# 最多保存的历史日志消息(单位:天)
maxAge = 28
# 日志文件名是否使用本地事件(否则使用UTC时间)
localTime = true
# 历史日志文件是否压缩(压缩格式为gz)
compress = true
# 是否打印调用源文件和行号
callerFile = false
# 是否打印调用方法
callerFunction = false
[blockchain]
defCacheSize=512
maxFetchBlockNum=128
timeoutSeconds=5
batchBlockNum=128
driver="leveldb"
dbPath="datadir"
dbCache=64
isStrongConsistency=true
singleMode=true
batchsync=false
enableTxQuickIndex=true
[p2p]
types=["dht"]
enable=false
msgCacheSize=10240
driver="leveldb"
dbPath="datadir/addrbook"
dbCache=4
grpcLogFile="grpc33.log"
[p2p.sub.dht]
channel=123
[rpc]
jrpcBindAddr="localhost:0"
grpcBindAddr="localhost:0"
whitelist=["127.0.0.1"]
jrpcFuncWhitelist=["*"]
grpcFuncWhitelist=["*"]
[mempool]
name="timeline"
poolCacheSize=10240
minTxFeeRate=100000
[consensus]
name="qbft"
minerstart=true
[mver.consensus]
fundKeyAddr = "1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5"
powLimitBits = "0x1f00ffff"
maxTxNumber = 1600 #160
[mver.consensus.ForkChainParamV1]
maxTxNumber = 10000
[mver.consensus.ForkChainParamV2]
powLimitBits = "0x1f2fffff"
[consensus.sub.qbft]
genesis="14KEKbYtKKQm4wMthSK9J4La4nAiidGozt"
genesisAmount=100000000
genesisBlockTime=1514533394
timeoutTxAvail=500
timeoutPropose=3000
timeoutProposeDelta=500
timeoutPrevote=2000
timeoutPrevoteDelta=500
timeoutPrecommit=2000
timeoutPrecommitDelta=500
timeoutCommit=500
skipTimeoutCommit=false
emptyBlockInterval=2
genesisFile="genesis_file.json"
privFile="priv_validator_0.json"
dbPath="datadir/qbft"
port=33001
validatorNodes=["127.0.0.1:33002"]
fastSync=true
# Propose阶段是否预执行区块
preExec=true
# 签名算法,支持"secp256k1","ed25519","sm2","bls",默认为"ed25519"
signName="bls"
# 是否使用聚合签名,签名算法需支持该特性,比如"bls"
useAggregateSignature=true
# 连续提议区块的个数,默认为1
multiBlocks=2
[store]
name="kvmvcc"
driver="leveldb"
dbPath="datadir/kvmvcc"
dbCache=128
[store.sub.kvmvcc]
enableMavlPrefix=false
enableMVCC=false
[wallet]
minFee=100000
driver="leveldb"
dbPath="wallet"
dbCache=16
signType="secp256k1"
[wallet.sub.ticket]
minerdisable=true
minerwhitelist=["*"]
[exec]
enableStat=false
enableMVCC=false
alias=["token1:token","token2:token","token3:token"]
saveTokenTxList=false
[exec.sub.cert]
# 是否启用证书验证和签名
enable=false
# 加密文件路径
cryptoPath="authdir/crypto"
# 带证书签名类型,支持"auth_ecdsa", "auth_sm2"
signType="auth_ecdsa"
[exec.sub.manage]
superManager=[
"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt",
]
[fork.system]
ForkExecKey=0
[fork.sub.manage]
Enable=0
ForkManageExec=0
[fork.sub.qbftNode]
Enable=0
[metrics]
#是否使能发送metrics数据的发送
enableMetrics=false
#数据保存模式
dataEmitMode="influxdb"
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 qbft
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
ttypes "github.com/33cn/plugin/plugin/consensus/qbft/types"
tmtypes "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
//-----------------------------------------------------------------------------
// BlockExecutor handles block execution and state updates.
// It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses,
// then commits and updates the mempool atomically, then saves state.
// BlockExecutor provides the context and accessories for properly executing a block.
type BlockExecutor struct {
// save state, validators, consensus params, abci responses here
db *CSStateDB
}
// NewBlockExecutor returns a new BlockExecutor with a NopEventBus.
// Call SetEventBus to provide one.
func NewBlockExecutor(db *CSStateDB) *BlockExecutor {
return &BlockExecutor{
db: db,
}
}
// ValidateBlock validates the given block against the given state.
// If the block is invalid, it returns an error.
// Validation does not mutate state, but does require historical information from the stateDB,
// ie. to verify evidence from a validator at an old height.
func (blockExec *BlockExecutor) ValidateBlock(s State, block *ttypes.QbftBlock) error {
return validateBlock(blockExec.db, s, block)
}
// ApplyBlock validates the block against the state, executes it against the app,
// fires the relevant events, commits the app, and saves the new state and responses.
// It's the only function that needs to be called
// from outside this package to process and commit an entire block.
// It takes a blockID to avoid recomputing the parts hash.
func (blockExec *BlockExecutor) ApplyBlock(s State, blockID ttypes.BlockID, block *ttypes.QbftBlock) (State, error) {
if err := blockExec.ValidateBlock(s, block); err != nil {
return s, fmt.Errorf("Commit failed for invalid block: %v", err)
}
// update the state with the block and responses
s, err := updateState(s, blockID, block)
if err != nil {
return s, fmt.Errorf("Commit failed for application: %v", err)
}
blockExec.db.SaveState(s)
return s, nil
}
// updateState returns a new QbftState updated according to the header and responses.
func updateState(s State, blockID ttypes.BlockID, block *ttypes.QbftBlock) (State, error) {
// copy the valset so we can apply changes from EndBlock
// and update s.LastValidators and s.Validators
prevValSet := s.Validators.Copy()
nextValSet := prevValSet.Copy()
// update the validator set with the latest abciResponses
lastHeightValsChanged := s.LastHeightValidatorsChanged
seq := s.Sequence + 1
// include situation multiBlock=1
if seq == multiBlocks.Load().(int64) {
// Update validator accums and set state variables
nextValSet.IncrementAccum(1)
seq = 0
}
// update the params with the latest abciResponses
nextParams := s.ConsensusParams
lastHeightParamsChanged := s.LastHeightConsensusParamsChanged
// NOTE: the AppHash has not been populated.
// It will be filled on state.Save.
return State{
ChainID: s.ChainID,
LastBlockHeight: block.Header.Height,
LastBlockTotalTx: s.LastBlockTotalTx + block.Header.NumTxs,
LastBlockID: blockID,
LastBlockTime: block.Header.Time,
Validators: nextValSet,
LastValidators: s.Validators.Copy(),
LastHeightValidatorsChanged: lastHeightValsChanged,
ConsensusParams: nextParams,
LastHeightConsensusParamsChanged: lastHeightParamsChanged,
LastResultsHash: nil,
AppHash: nil,
Sequence: seq,
LastSequence: s.Sequence,
LastCommitRound: block.Header.Round,
}, nil
}
func updateValidators(currentSet *ttypes.ValidatorSet, updates []*tmtypes.QbftNode) error {
// If more or equal than 1/3 of total voting power changed in one block, then
// a light client could never prove the transition externally. See
// ./lite/doc.go for details on how a light client tracks validators.
vp23, err := changeInVotingPowerMoreOrEqualToOneThird(currentSet, updates)
if err != nil {
return err
}
if vp23 {
return errors.New("the change in voting power must be strictly less than 1/3")
}
for _, v := range updates {
pubkeyBytes, err := hex.DecodeString(v.PubKey)
if err != nil {
return err
}
pubkey, err := ttypes.ConsensusCrypto.PubKeyFromBytes(pubkeyBytes)
if err != nil {
return err
}
address := ttypes.GenAddressByPubKey(pubkey)
power := v.Power
// mind the overflow from int64
if power < 0 {
return fmt.Errorf("Power (%d) overflows int64", v.Power)
}
_, val := currentSet.GetByAddress(address)
if val == nil {
// add val
added := currentSet.Add(ttypes.NewValidator(pubkey, power))
if !added {
return fmt.Errorf("Failed to add new validator %X with voting power %d", address, power)
}
} else if v.Power == 0 {
// remove val
_, removed := currentSet.Remove(address)
if !removed {
return fmt.Errorf("Failed to remove validator %X", address)
}
} else {
// update val
val.VotingPower = power
updated := currentSet.Update(val)
if !updated {
return fmt.Errorf("Failed to update validator %X with voting power %d", address, power)
}
}
}
return nil
}
func changeInVotingPowerMoreOrEqualToOneThird(currentSet *ttypes.ValidatorSet, updates []*tmtypes.QbftNode) (bool, error) {
threshold := currentSet.TotalVotingPower() * 1 / 3
acc := int64(0)
for _, v := range updates {
pubkeyBytes, err := hex.DecodeString(v.PubKey)
if err != nil {
return false, err
}
pubkey, err := ttypes.ConsensusCrypto.PubKeyFromBytes(pubkeyBytes)
if err != nil {
return false, err
}
address := ttypes.GenAddressByPubKey(pubkey)
power := v.Power
// mind the overflow from int64
if power < 0 {
return false, fmt.Errorf("Power (%d) overflows int64", v.Power)
}
_, val := currentSet.GetByAddress(address)
if val == nil {
acc += power
} else {
np := val.VotingPower - power
if np < 0 {
np = -np
}
acc += np
}
if acc >= threshold {
return true, nil
}
}
return false, nil
}
func validateBlock(stateDB *CSStateDB, s State, b *ttypes.QbftBlock) error {
// Validate internal consistency.
if err := b.ValidateBasic(); err != nil {
return err
}
// validate basic info
if b.Header.ChainID != s.ChainID {
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.Header.ChainID)
}
if b.Header.Height != s.LastBlockHeight+1 {
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.LastBlockHeight+1, b.Header.Height)
}
// validate prev block info
if !bytes.Equal(b.Header.LastBlockID.Hash, s.LastBlockID.Hash) {
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.Header.LastBlockID)
}
newTxs := b.Header.NumTxs
if b.Header.TotalTxs != s.LastBlockTotalTx+newTxs {
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.Header.TotalTxs)
}
// validate app info
if !bytes.Equal(b.Header.AppHash, s.AppHash) {
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.Header.AppHash)
}
if !bytes.Equal(b.Header.ConsensusHash, s.ConsensusParams.Hash()) {
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.Header.ConsensusHash)
}
if !bytes.Equal(b.Header.LastResultsHash, s.LastResultsHash) {
return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.Header.LastResultsHash)
}
if !bytes.Equal(b.Header.ValidatorsHash, s.Validators.Hash()) {
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.Header.ValidatorsHash)
}
// Validate block LastCommit.
if b.Header.Height == 1 {
if len(b.LastCommit.Precommits) != 0 {
return errors.New("Block at height 1 (first block) not have LastCommit precommits")
}
} else {
if (b.Header.LastSequence == 0 && b.LastCommit.VoteType != uint32(ttypes.VoteTypePrecommit)) ||
(b.Header.LastSequence > 0 && b.LastCommit.VoteType != uint32(ttypes.VoteTypePrevote)) {
return fmt.Errorf("Wrong LastCommit VoteType. LastSequence %v, VoteType %v",
b.Header.LastSequence, b.LastCommit.VoteType)
}
lastCommit := &ttypes.Commit{QbftCommit: b.LastCommit}
err := s.LastValidators.VerifyCommit(s.ChainID, s.LastBlockID, b.Header.Height-1, lastCommit)
if err != nil {
return err
}
}
return nil
}
{"genesis_time":"2021-04-08T18:14:39.801783077+08:00","chain_id":"chain33-4zQKxd","validators":[{"pub_key":{"type":"bls","data":"982CCC34E95551D9B0C08426CCAAF0BAEC5A80F1974B19F5FF8F5FA12A5CA211E194652D669F3A500D25E87B22850261"},"power":10,"name":""},{"pub_key":{"type":"bls","data":"B8592E9CB9FB8CB8297FA1A5F5472A792E92F30FA846136A3A2733B7B4E2805938557C790EF019AF5693ADA84E94BF7B"},"power":10,"name":""}],"app_hash":null}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
{"address":"2BA80E3DE367F01E438BED8718861D390848CCC3","pub_key":{"type":"bls","data":"982CCC34E95551D9B0C08426CCAAF0BAEC5A80F1974B19F5FF8F5FA12A5CA211E194652D669F3A500D25E87B22850261"},"last_height":0,"last_round":0,"last_step":0,"priv_key":{"type":"bls","data":"655DFC400845AC6997B959CA3F26456A644C0C18CF3ABC93345DDE19B5FB939A"}}
\ No newline at end of file
{"address":"853414DE8CFA9B4C8F96C09DD0CAE30E8514AD31","pub_key":{"type":"bls","data":"B8592E9CB9FB8CB8297FA1A5F5472A792E92F30FA846136A3A2733B7B4E2805938557C790EF019AF5693ADA84E94BF7B"},"last_height":0,"last_round":0,"last_step":0,"priv_key":{"type":"bls","data":"53BAFC4FA4487024ADFA69C1593C0EF67DD89CFC57CA187CAE84B48E885A8B4B"}}
\ No newline at end of file
This diff is collapsed.
package qbft
import (
"fmt"
"os"
"testing"
"time"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/common/crypto"
rpctypes "github.com/33cn/chain33/rpc/types"
mty "github.com/33cn/chain33/system/dapp/manage/types"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/33cn/chain33/util/testnode"
vty "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
"github.com/stretchr/testify/assert"
//加载系统内置store, 不要依赖plugin
_ "github.com/33cn/chain33/system"
_ "github.com/33cn/chain33/system/dapp/init"
_ "github.com/33cn/chain33/system/mempool/init"
_ "github.com/33cn/chain33/system/store/init"
_ "github.com/33cn/plugin/plugin/dapp/init"
_ "github.com/33cn/plugin/plugin/store/init"
)
var quitC chan struct{}
func init() {
quitC = make(chan struct{}, 1)
}
// 执行: go test -cover
func TestQbft(t *testing.T) {
mock33 := testnode.New("chain33.qbft.toml", nil)
cfg := mock33.GetClient().GetConfig()
mock33.Listen()
t.Log(mock33.GetGenesisAddress())
go startNode(t)
time.Sleep(2 * time.Second)
configTx := configManagerTx()
mock33.GetAPI().SendTx(configTx)
mock33.WaitTx(configTx.Hash())
addTx := addNodeTx()
mock33.GetAPI().SendTx(addTx)
mock33.WaitTx(addTx.Hash())
txs := util.GenNoneTxs(cfg, mock33.GetGenesisKey(), 4)
for i := 0; i < len(txs); i++ {
mock33.GetAPI().SendTx(txs[i])
mock33.WaitTx(txs[i].Hash())
}
testQuery(t, mock33)
quitC <- struct{}{}
mock33.Close()
clearQbftData("datadir")
time.Sleep(2 * time.Second)
}
func startNode(t *testing.T) {
cfg2 := types.NewChain33Config(types.ReadFile("chain33.qbft.toml"))
sub := cfg2.GetSubConfig()
qcfg, err := types.ModifySubConfig(sub.Consensus["qbft"], "privFile", "priv_validator_1.json")
assert.Nil(t, err)
qcfg, err = types.ModifySubConfig(qcfg, "dbPath", "datadir2/qbft")
assert.Nil(t, err)
qcfg, err = types.ModifySubConfig(qcfg, "port", 33002)
assert.Nil(t, err)
qcfg, err = types.ModifySubConfig(qcfg, "validatorNodes", []string{"127.0.0.1:33001"})
assert.Nil(t, err)
sub.Consensus["qbft"] = qcfg
mock33_2 := testnode.NewWithConfig(cfg2, nil)
defer clearQbftData("datadir2")
defer mock33_2.Close()
for {
select {
case <-quitC:
fmt.Println("node2 will stop")
return
default:
txs := util.GenNoneTxs(cfg2, mock33_2.GetGenesisKey(), 1)
mock33_2.GetAPI().SendTx(txs[0])
time.Sleep(500 * time.Millisecond)
}
}
}
func addNodeTx() *types.Transaction {
pubkey := "93E69B00BCBC817BE7E3370BA0228908C6F5E5458F781998CDD2FDF7A983EB18BCF57F838901026DC65EDAC9A1F3D251"
nput := &vty.QbftNodeAction_Node{Node: &vty.QbftNode{PubKey: pubkey, Power: int64(5)}}
action := &vty.QbftNodeAction{Value: nput, Ty: vty.QbftNodeActionUpdate}
tx := &types.Transaction{Execer: []byte("qbftNode"), Payload: types.Encode(action), Fee: fee}
tx.To = address.ExecAddress("qbftNode")
tx.Sign(types.SECP256K1, getprivkey("CC38546E9E659D15E6B4893F0AB32A06D103931A8230B0BDE71459D2B27D6944"))
return tx
}
func configManagerTx() *types.Transaction {
v := &types.ModifyConfig{Key: "qbft-manager", Op: "add", Value: "14KEKbYtKKQm4wMthSK9J4La4nAiidGozt", Addr: ""}
modify := &mty.ManageAction{
Ty: mty.ManageActionModifyConfig,
Value: &mty.ManageAction_Modify{Modify: v},
}
tx := &types.Transaction{Execer: []byte("manage"), Payload: types.Encode(modify), Fee: fee}
tx.To = address.ExecAddress("manage")
tx.Sign(types.SECP256K1, getprivkey("CC38546E9E659D15E6B4893F0AB32A06D103931A8230B0BDE71459D2B27D6944"))
return tx
}
func getprivkey(key string) crypto.PrivKey {
cr, err := crypto.New(types.GetSignName("", types.SECP256K1))
if err != nil {
panic(err)
}
bkey, err := common.FromHex(key)
if err != nil {
panic(err)
}
priv, err := cr.PrivKeyFromBytes(bkey[:32])
if err != nil {
panic(err)
}
return priv
}
func testQuery(t *testing.T, mock *testnode.Chain33Mock) {
var flag bool
err := mock.GetJSONC().Call("qbftNode.IsSync", &types.ReqNil{}, &flag)
assert.Nil(t, err)
assert.Equal(t, true, flag)
var qstate vty.QbftState
query := &rpctypes.Query4Jrpc{
Execer: vty.QbftNodeX,
FuncName: "GetCurrentState",
}
err = mock.GetJSONC().Call("Chain33.Query", query, &qstate)
assert.Nil(t, err)
assert.Len(t, qstate.Validators.Validators, 3)
state := LoadState(&qstate)
_, curVals := state.GetValidators()
assert.Len(t, curVals.Validators, 3)
assert.True(t, state.Equals(state.Copy()))
var reply vty.QbftNodeInfoSet
err = mock.GetJSONC().Call("qbftNode.GetNodeInfo", &types.ReqNil{}, &reply)
assert.Nil(t, err)
assert.Len(t, reply.Nodes, 3)
}
func clearQbftData(path string) {
err := os.RemoveAll(path)
if err != nil {
fmt.Println("qbft data clear fail", err.Error())
}
fmt.Println("qbft data clear success")
}
// 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 qbftNode Uses nacl's secret_box to encrypt a net.Conn.
// It is (meant to be) an implementation of the STS protocol.
// Note we do not (yet) assume that a remote peer's pubkey
// is known ahead of time, and thus we are technically
// still vulnerable to MITM. (TODO!)
// See docs/sts-final.pdf for more info
package qbft
import (
"bytes"
crand "crypto/rand"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"time"
"github.com/33cn/chain33/common/crypto"
ttypes "github.com/33cn/plugin/plugin/consensus/qbft/types"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/ripemd160"
)
// 2 + 1024 == 1026 total frame size
const (
dataLenSize = 2 // uint16 to describe the length, is <= dataMaxSize
dataMaxSize = 1024
totalFrameSize = dataMaxSize + dataLenSize
sealedFrameSize = totalFrameSize + secretbox.Overhead
) // fixed size (length prefixed) byte arrays
// SecretConnection Implements net.Conn
type SecretConnection struct {
conn io.ReadWriteCloser
recvBuffer []byte
recvNonce *[24]byte
sendNonce *[24]byte
remPubKey crypto.PubKey
shrSecret *[32]byte // shared secret
}
// MakeSecretConnection Performs handshake and returns a new authenticated SecretConnection.
// Returns nil if error in handshake.
// Caller should call conn.Close()
// See docs/sts-final.pdf for more information.
func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) {
locPubKey := locPrivKey.PubKey()
// Generate ephemeral keys for perfect forward secrecy.
locEphPub, locEphPriv := genEphKeys()
// Write local ephemeral pubkey and receive one too.
// NOTE: every 32-byte string is accepted as a Curve25519 public key
// (see DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
remEphPub, err := shareEphPubKey(conn, locEphPub)
if err != nil {
return nil, fmt.Errorf("shareEphPubKey: %v", err)
}
// Compute common shared secret.
shrSecret := computeSharedSecret(remEphPub, locEphPriv)
// Sort by lexical order.
loEphPub, hiEphPub := sort32(locEphPub, remEphPub)
// Check if the local ephemeral public key
// was the least, lexicographically sorted.
locIsLeast := bytes.Equal(locEphPub[:], loEphPub[:])
// Generate nonces to use for secretbox.
recvNonce, sendNonce := genNonces(loEphPub, hiEphPub, locIsLeast)
// Generate common challenge to sign.
challenge := genChallenge(loEphPub, hiEphPub)
// Construct SecretConnection.
sc := &SecretConnection{
conn: conn,
recvBuffer: nil,
recvNonce: recvNonce,
sendNonce: sendNonce,
shrSecret: shrSecret,
}
// Sign the challenge bytes for authentication.
locSignature := signChallenge(challenge, locPrivKey)
// Share (in secret) each other's pubkey & challenge signature
authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
if err != nil {
return nil, fmt.Errorf("shareAuthSignature: %v", err)
}
remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig
if !remPubKey.VerifyBytes(challenge[:], remSignature) {
return nil, errors.New("Challenge verification failed")
}
// We've authorized.
sc.remPubKey = remPubKey
return sc, nil
}
// RemotePubKey Returns authenticated remote pubkey
func (sc *SecretConnection) RemotePubKey() crypto.PubKey {
return sc.remPubKey
}
// Writes encrypted frames of `sealedFrameSize`
// CONTRACT: data smaller than dataMaxSize is read atomically.
func (sc *SecretConnection) Write(data []byte) (n int, err error) {
for 0 < len(data) {
var frame = make([]byte, totalFrameSize)
var chunk []byte
if dataMaxSize < len(data) {
chunk = data[:dataMaxSize]
data = data[dataMaxSize:]
} else {
chunk = data
data = nil
}
chunkLength := len(chunk)
binary.BigEndian.PutUint16(frame, uint16(chunkLength))
copy(frame[dataLenSize:], chunk)
// encrypt the frame
var sealedFrame = make([]byte, sealedFrameSize)
secretbox.Seal(sealedFrame[:0], frame, sc.sendNonce, sc.shrSecret)
incr2Nonce(sc.sendNonce)
// end encryption
_, err := sc.conn.Write(sealedFrame)
if err != nil {
return n, err
}
n += len(chunk)
}
return
}
// CONTRACT: data smaller than dataMaxSize is read atomically.
func (sc *SecretConnection) Read(data []byte) (n int, err error) {
if 0 < len(sc.recvBuffer) {
count := copy(data, sc.recvBuffer)
sc.recvBuffer = sc.recvBuffer[count:]
return
}
sealedFrame := make([]byte, sealedFrameSize)
_, err = io.ReadFull(sc.conn, sealedFrame)
if err != nil {
return
}
// decrypt the frame
var frame = make([]byte, totalFrameSize)
_, ok := secretbox.Open(frame[:0], sealedFrame, sc.recvNonce, sc.shrSecret)
if !ok {
return n, errors.New("Failed to decrypt SecretConnection")
}
incr2Nonce(sc.recvNonce)
// end decryption
var chunkLength = binary.BigEndian.Uint16(frame) // read the first two bytes
if chunkLength > dataMaxSize {
return 0, errors.New("chunkLength is greater than dataMaxSize")
}
var chunk = frame[dataLenSize : dataLenSize+chunkLength]
n = copy(data, chunk)
sc.recvBuffer = chunk[n:]
return
}
// Close Implements net.Conn
func (sc *SecretConnection) Close() error { return sc.conn.Close() }
// LocalAddr ...
func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() }
// RemoteAddr ...
func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() }
// SetDeadline ...
func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
// SetReadDeadline ...
func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
return sc.conn.(net.Conn).SetReadDeadline(t)
}
// SetWriteDeadline ...
func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
return sc.conn.(net.Conn).SetWriteDeadline(t)
}
func genEphKeys() (ephPub, ephPriv *[32]byte) {
var err error
ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
if err != nil {
ttypes.PanicCrisis("Could not generate ephemeral keypairs")
}
return
}
func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
var err1, err2 error
Parallel(
func() {
_, err1 = conn.Write(locEphPub[:])
},
func() {
remEphPub = new([32]byte)
_, err2 = io.ReadFull(conn, remEphPub[:])
},
)
if err1 != nil {
return nil, err1
}
if err2 != nil {
return nil, err2
}
return remEphPub, nil
}
func computeSharedSecret(remPubKey, locPrivKey *[32]byte) (shrSecret *[32]byte) {
shrSecret = new([32]byte)
box.Precompute(shrSecret, remPubKey, locPrivKey)
return
}
func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
if bytes.Compare(foo[:], bar[:]) < 0 {
lo = foo
hi = bar
} else {
lo = bar
hi = foo
}
return
}
func genNonces(loPubKey, hiPubKey *[32]byte, locIsLo bool) (recvNonce, sendNonce *[24]byte) {
nonce1 := hash24(append(loPubKey[:], hiPubKey[:]...))
nonce2 := new([24]byte)
copy(nonce2[:], nonce1[:])
nonce2[len(nonce2)-1] ^= 0x01
if locIsLo {
recvNonce = nonce1
sendNonce = nonce2
} else {
recvNonce = nonce2
sendNonce = nonce1
}
return
}
func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) {
return hash32(append(loPubKey[:], hiPubKey[:]...))
}
func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature crypto.Signature) {
signature = locPrivKey.Sign(challenge[:])
return
}
type authSigMessage struct {
Key crypto.PubKey
Sig crypto.Signature
}
func shareAuthSignature(sc io.ReadWriter, pubKey crypto.PubKey, signature crypto.Signature) (*authSigMessage, error) {
var recvMsg authSigMessage
var err1, err2 error
pubLen := len(pubKey.Bytes())
sigLen := len(signature.Bytes())
Parallel(
func() {
msgByte := make([]byte, pubLen+sigLen)
copy(msgByte, pubKey.Bytes()[:pubLen])
copy(msgByte[pubLen:], signature.Bytes())
_, err1 = sc.Write(msgByte)
},
func() {
readBuffer := make([]byte, pubLen+sigLen)
_, err2 = io.ReadFull(sc, readBuffer)
if err2 != nil {
return
}
recvMsg.Key, err2 = ttypes.ConsensusCrypto.PubKeyFromBytes(readBuffer[:pubLen])
if err2 != nil {
return
}
recvMsg.Sig, err2 = ttypes.ConsensusCrypto.SignatureFromBytes(readBuffer[pubLen:])
if err2 != nil {
return
}
})
if err1 != nil {
return nil, err1
}
if err2 != nil {
return nil, err2
}
return &recvMsg, nil
}
//--------------------------------------------------------------------------------
// sha256
func hash32(input []byte) (res *[32]byte) {
hasher := sha256.New()
_, err := hasher.Write(input) // nolint: errcheck, gas
if err != nil {
panic(err)
}
resSlice := hasher.Sum(nil)
res = new([32]byte)
copy(res[:], resSlice)
return
}
// We only fill in the first 20 bytes with ripemd160
func hash24(input []byte) (res *[24]byte) {
hasher := ripemd160.New()
_, err := hasher.Write(input) // nolint: errcheck, gas
if err != nil {
panic(err)
}
resSlice := hasher.Sum(nil)
res = new([24]byte)
copy(res[:], resSlice)
return
}
// increment nonce big-endian by 2 with wraparound.
func incr2Nonce(nonce *[24]byte) {
incrNonce(nonce)
incrNonce(nonce)
}
// increment nonce big-endian by 1 with wraparound.
func incrNonce(nonce *[24]byte) {
for i := 23; 0 <= i; i-- {
nonce[i]++
if nonce[i] != 0 {
return
}
}
}
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 qbft
import (
"fmt"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
tmtypes "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
"github.com/golang/protobuf/proto"
)
var (
stateKey = []byte("stateKey")
)
//ConsensusStore ...
type ConsensusStore struct {
db dbm.DB
}
// NewConsensusStore returns a new ConsensusStore with the given DB
func NewConsensusStore() *ConsensusStore {
db := DefaultDBProvider("state")
db.SetCacheSize(100)
return &ConsensusStore{
db: db,
}
}
// LoadStateFromStore ...
func (cs *ConsensusStore) LoadStateFromStore() *tmtypes.QbftState {
buf, err := cs.db.Get(stateKey)
if err != nil {
qbftlog.Error("LoadStateFromStore", "err", err)
return nil
}
state := &tmtypes.QbftState{}
err = types.Decode(buf, state)
if err != nil {
panic(err)
}
return state
}
// LoadStateHeight ...
func (cs *ConsensusStore) LoadStateHeight() int64 {
state := cs.LoadStateFromStore()
if state == nil {
return int64(0)
}
return state.LastBlockHeight
}
// LoadSeenCommit by height
func (cs *ConsensusStore) LoadSeenCommit(height int64) *tmtypes.QbftCommit {
buf, err := cs.db.Get(calcSeenCommitKey(height))
if err != nil {
qbftlog.Error("LoadSeenCommit", "err", err)
return nil
}
commit := &tmtypes.QbftCommit{}
err = types.Decode(buf, commit)
if err != nil {
panic(err)
}
return commit
}
// SaveConsensusState save state and seenCommit
func (cs *ConsensusStore) SaveConsensusState(height int64, state *tmtypes.QbftState, sc proto.Message) error {
seenCommitBytes := types.Encode(sc)
stateBytes := types.Encode(state)
batch := cs.db.NewBatch(true)
batch.Set(calcSeenCommitKey(height), seenCommitBytes)
batch.Set(stateKey, stateBytes)
err := batch.Write()
if err != nil {
qbftlog.Error("SaveConsensusState batch.Write", "err", err)
return err
}
return nil
}
func calcSeenCommitKey(height int64) []byte {
return []byte(fmt.Sprintf("SC:%v", height))
}
// 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 qbft
import (
"time"
)
var (
tickTockBufferSize = 10
)
// TimeoutTicker is a timer that schedules timeouts
// conditional on the height/round/step in the timeoutInfo.
// The timeoutInfo.Duration may be non-positive.
type TimeoutTicker interface {
Start()
Stop()
Chan() <-chan timeoutInfo // on which to receive a timeout
ScheduleTimeout(ti timeoutInfo) // reset the timer
}
// timeoutTicker wraps time.Timer,
// scheduling timeouts only for greater height/round/step
// than what it's already seen.
// Timeouts are scheduled along the tickChan,
// and fired on the tockChan.
type timeoutTicker struct {
timer *time.Timer
tickChan chan timeoutInfo // for scheduling timeouts
tockChan chan timeoutInfo // for notifying about them
}
// NewTimeoutTicker returns a new TimeoutTicker.
func NewTimeoutTicker() TimeoutTicker {
tt := &timeoutTicker{
timer: time.NewTimer(0),
tickChan: make(chan timeoutInfo, tickTockBufferSize),
tockChan: make(chan timeoutInfo, tickTockBufferSize),
}
tt.stopTimer() // don't want to fire until the first scheduled timeout
return tt
}
// OnStart implements cmn.Service. It starts the timeout routine.
func (t *timeoutTicker) Start() {
go t.timeoutRoutine()
}
// OnStop implements cmn.Service. It stops the timeout routine.
func (t *timeoutTicker) Stop() {
t.stopTimer()
}
// Chan returns a channel on which timeouts are sent.
func (t *timeoutTicker) Chan() <-chan timeoutInfo {
return t.tockChan
}
// ScheduleTimeout schedules a new timeout by sending on the internal tickChan.
// The timeoutRoutine is always available to read from tickChan, so this won't block.
// The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step.
func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) {
t.tickChan <- ti
}
//-------------------------------------------------------------
// stop the timer and drain if necessary
func (t *timeoutTicker) stopTimer() {
// Stop() returns false if it was already fired or was stopped
if !t.timer.Stop() {
select {
case <-t.timer.C:
default:
qbftlog.Debug("Timer already stopped")
}
}
}
// send on tickChan to start a new timer.
// timers are interupted and replaced by new ticks from later steps
// timeouts of 0 on the tickChan will be immediately relayed to the tockChan
func (t *timeoutTicker) timeoutRoutine() {
qbftlog.Debug("Starting timeout routine")
var ti timeoutInfo
for {
select {
case newti := <-t.tickChan:
qbftlog.Debug("Received tick", "old_ti", ti, "new_ti", newti)
// ignore tickers for old height/round/step
if newti.Height < ti.Height {
continue
} else if newti.Height == ti.Height {
if newti.Round < ti.Round {
continue
} else if newti.Round == ti.Round {
if ti.Step > 0 && newti.Step <= ti.Step {
continue
}
}
}
// stop the last timer
t.stopTimer()
// update timeoutInfo and reset timer
// NOTE time.Timer allows duration to be non-positive
ti = newti
t.timer.Reset(ti.Duration)
qbftlog.Debug("Scheduled timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
case <-t.timer.C:
qbftlog.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
// go routine here guarantees timeoutRoutine doesn't block.
// Determinism comes from playback in the receiveRoutine.
// We can eliminate it by merging the timeoutRoutine into receiveRoutine
// and managing the timeouts ourselves with a millisecond ticker
go func(toi timeoutInfo) { t.tockChan <- toi }(ti)
}
}
}
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 types
import (
"errors"
"github.com/33cn/chain33/common/crypto"
"github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
//authbls register
const (
AuthBLS = 259
)
var (
// ErrHeightLessThanOne error type
ErrHeightLessThanOne = errors.New("ErrHeightLessThanOne")
// ErrBaseTxType error type
ErrBaseTxType = errors.New("ErrBaseTxType")
// ErrBlockInfoTx error type
ErrBlockInfoTx = errors.New("ErrBlockInfoTx")
// ErrBaseExecErr error type
ErrBaseExecErr = errors.New("ErrBaseExecErr")
// ErrLastBlockID error type
ErrLastBlockID = errors.New("ErrLastBlockID")
)
var (
ttlog = log15.New("module", "qbft-types")
//ConsensusCrypto define
ConsensusCrypto crypto.Crypto
//CryptoName ...
CryptoName string
// SignMap define sign type
SignMap = map[string]int{
"secp256k1": types.SECP256K1,
"ed25519": types.ED25519,
"sm2": types.SM2,
"bls": AuthBLS,
}
)
// 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 (
"encoding/json"
"io/ioutil"
"time"
"github.com/pkg/errors"
)
//------------------------------------------------------------
// core types for a genesis definition
// GenesisValidator is an initial validator.
type GenesisValidator struct {
PubKey KeyText `json:"pub_key"`
Power int64 `json:"power"`
Name string `json:"name"`
}
// GenesisDoc defines the initial conditions for a qbftNode blockchain, in particular its validator set.
type GenesisDoc struct {
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
ConsensusParams *ConsensusParams `json:"consensus_params,omitempty"`
Validators []GenesisValidator `json:"validators"`
AppHash []byte `json:"app_hash"`
AppOptions interface{} `json:"app_options,omitempty"`
}
// SaveAs is a utility method for saving GenensisDoc as a JSON file.
func (genDoc *GenesisDoc) SaveAs(file string) error {
genDocBytes, err := json.Marshal(genDoc)
if err != nil {
return err
}
return WriteFile(file, genDocBytes, 0644)
}
// ValidatorHash returns the hash of the validator set contained in the GenesisDoc
func (genDoc *GenesisDoc) ValidatorHash() []byte {
vals := make([]*Validator, len(genDoc.Validators))
for i, v := range genDoc.Validators {
if len(v.PubKey.Data) == 0 {
panic(Fmt("ValidatorHash pubkey of validator[%v] in gendoc is empty", i))
}
pubkey, err := PubKeyFromString(v.PubKey.Data)
if err != nil {
panic(Fmt("ValidatorHash PubKeyFromBytes failed:%v", err))
}
vals[i] = NewValidator(pubkey, v.Power)
}
vset := NewValidatorSet(vals)
return vset.Hash()
}
// ValidateAndComplete checks that all necessary fields are present
// and fills in defaults for optional fields left empty
func (genDoc *GenesisDoc) ValidateAndComplete() error {
if genDoc.ChainID == "" {
return errors.Errorf("Genesis doc must include non-empty chain_id")
}
if genDoc.ConsensusParams == nil {
genDoc.ConsensusParams = DefaultConsensusParams()
} else {
if err := genDoc.ConsensusParams.Validate(); err != nil {
return err
}
}
if len(genDoc.Validators) == 0 {
return errors.Errorf("The genesis file must have at least one validator")
}
for _, v := range genDoc.Validators {
if v.Power == 0 {
return errors.Errorf("The genesis file cannot contain validators with no voting power: %v", v)
}
}
if genDoc.GenesisTime.IsZero() {
genDoc.GenesisTime = time.Now()
}
return nil
}
//------------------------------------------------------------
// Make genesis state from file
// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
genDoc := GenesisDoc{}
err := json.Unmarshal(jsonBlob, &genDoc)
if err != nil {
return nil, err
}
if err := genDoc.ValidateAndComplete(); err != nil {
return nil, err
}
return &genDoc, err
}
// GenesisDocFromFile reads JSON data from a file and unmarshalls it into a GenesisDoc.
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
jsonBlob, err := ioutil.ReadFile(genDocFile)
if err != nil {
return nil, errors.Wrap(err, "Couldn't read GenesisDoc file")
}
genDoc, err := GenesisDocFromJSON(jsonBlob)
if err != nil {
return nil, errors.Wrap(err, Fmt("Error reading GenesisDoc at %v", genDocFile))
}
return genDoc, nil
}
// 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"
"strings"
"sync"
tmtypes "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// RoundVoteSet ...
type RoundVoteSet struct {
Prevotes *VoteSet
Precommits *VoteSet
}
/*
Keeps track of all VoteSets from round 0 to round 'round'.
Also keeps track of up to one RoundVoteSet greater than
'round' from each peer, to facilitate catchup syncing of commits.
A commit is +2/3 precommits for a block at a round,
but which round is not known in advance, so when a peer
provides a precommit for a round greater than mtx.round,
we create a new entry in roundVoteSets but also remember the
peer to prevent abuse.
We let each peer provide us with up to 2 unexpected "catchup" rounds.
One for their LastCommit round, and another for the official commit round.
*/
// HeightVoteSet ...
type HeightVoteSet struct {
chainID string
height int64
valSet *ValidatorSet
mtx sync.Mutex
round int // max tracked round
roundVoteSets map[int]RoundVoteSet // keys: [0...round]
peerCatchupRounds map[string][]int // keys: peer.Key; values: at most 2 rounds
}
// NewHeightVoteSet ...
func NewHeightVoteSet(chainID string, height int64, valSet *ValidatorSet) *HeightVoteSet {
hvs := &HeightVoteSet{
chainID: chainID,
}
hvs.Reset(height, valSet)
return hvs
}
// Reset ...
func (hvs *HeightVoteSet) Reset(height int64, valSet *ValidatorSet) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
hvs.height = height
hvs.valSet = valSet
hvs.roundVoteSets = make(map[int]RoundVoteSet)
hvs.peerCatchupRounds = make(map[string][]int)
hvs.addRound(0)
hvs.round = 0
}
// Height ...
func (hvs *HeightVoteSet) Height() int64 {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
return hvs.height
}
// Round ...
func (hvs *HeightVoteSet) Round() int {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
return hvs.round
}
// SetRound Create more RoundVoteSets up to round.
func (hvs *HeightVoteSet) SetRound(round int) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
if hvs.round != 0 && (round < hvs.round+1) {
panic(Fmt("Panicked on a Sanity Check: %v", "SetRound() must increment hvs.round"))
}
for r := hvs.round + 1; r <= round; r++ {
if _, ok := hvs.roundVoteSets[r]; ok {
continue // Already exists because peerCatchupRounds.
}
hvs.addRound(r)
}
hvs.round = round
}
func (hvs *HeightVoteSet) addRound(round int) {
if _, ok := hvs.roundVoteSets[round]; ok {
panic("addRound() for an existing round")
}
prevotes := NewVoteSet(hvs.chainID, hvs.height, round, VoteTypePrevote, hvs.valSet)
precommits := NewVoteSet(hvs.chainID, hvs.height, round, VoteTypePrecommit, hvs.valSet)
hvs.roundVoteSets[round] = RoundVoteSet{
Prevotes: prevotes,
Precommits: precommits,
}
}
// AddVote Duplicate votes return added=false, err=nil.
// By convention, peerKey is "" if origin is self.
func (hvs *HeightVoteSet) AddVote(vote *Vote, peerID string) (added bool, err error) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
if !IsVoteTypeValid(byte(vote.Type)) {
return
}
round := int(vote.Round)
voteSet := hvs.getVoteSet(round, byte(vote.Type))
if voteSet == nil {
if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 {
hvs.addRound(int(vote.Round))
voteSet = hvs.getVoteSet(round, byte(vote.Type))
hvs.peerCatchupRounds[peerID] = append(rndz, round)
} else {
// Peer has sent a vote that does not match our round,
// for more than one round. Bad peer!
// TODO punish peer.
// log.Warn("Deal with peer giving votes from unwanted rounds")
err = errors.New("Peer has sent a vote that does not match our round for more than one round")
return
}
}
added, err = voteSet.AddVote(vote)
return
}
// AddAggVote Duplicate votes return added=false, err=nil.
// By convention, peerKey is "" if origin is self.
func (hvs *HeightVoteSet) AddAggVote(vote *AggVote, peerID string) (added bool, err error) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
if !IsVoteTypeValid(byte(vote.Type)) {
return
}
round := int(vote.Round)
voteSet := hvs.getVoteSet(round, byte(vote.Type))
if voteSet == nil {
if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 {
hvs.addRound(int(vote.Round))
voteSet = hvs.getVoteSet(round, byte(vote.Type))
hvs.peerCatchupRounds[peerID] = append(rndz, round)
} else {
err = errors.New("Peer has sent a aggregate vote that does not match our round for more than one round")
return
}
}
added, err = voteSet.AddAggVote(vote)
return
}
// Prevotes ...
func (hvs *HeightVoteSet) Prevotes(round int) *VoteSet {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
return hvs.getVoteSet(round, VoteTypePrevote)
}
// Precommits ...
func (hvs *HeightVoteSet) Precommits(round int) *VoteSet {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
return hvs.getVoteSet(round, VoteTypePrecommit)
}
// POLInfo Last round and blockID that has +2/3 prevotes for a particular block or nil.
// Returns -1 if no such round exists.
func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID BlockID) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
for r := hvs.round; r >= 0; r-- {
rvs := hvs.getVoteSet(r, VoteTypePrevote)
polBlockID, ok := rvs.TwoThirdsMajority()
if ok {
return r, BlockID{&polBlockID}
}
}
return -1, BlockID{}
}
func (hvs *HeightVoteSet) getVoteSet(round int, voteType byte) *VoteSet {
rvs, ok := hvs.roundVoteSets[round]
if !ok {
return nil
}
switch voteType {
case VoteTypePrevote:
return rvs.Prevotes
case VoteTypePrecommit:
return rvs.Precommits
default:
panic(Fmt("Unexpected vote type %X", voteType))
}
}
func (hvs *HeightVoteSet) String() string {
return hvs.StringIndented("")
}
// StringIndented ...
func (hvs *HeightVoteSet) StringIndented(indent string) string {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
vsStrings := make([]string, 0, (len(hvs.roundVoteSets)+1)*2)
// rounds 0 ~ hvs.round inclusive
for round := 0; round <= hvs.round; round++ {
voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort()
vsStrings = append(vsStrings, voteSetString)
voteSetString = hvs.roundVoteSets[round].Precommits.StringShort()
vsStrings = append(vsStrings, voteSetString)
}
// all other peer catchup rounds
for round, roundVoteSet := range hvs.roundVoteSets {
if round <= hvs.round {
continue
}
voteSetString := roundVoteSet.Prevotes.StringShort()
vsStrings = append(vsStrings, voteSetString)
voteSetString = roundVoteSet.Precommits.StringShort()
vsStrings = append(vsStrings, voteSetString)
}
return Fmt(`HeightVoteSet{H:%v R:0~%v
%s %v
%s}`,
hvs.height, hvs.round,
indent, strings.Join(vsStrings, "\n"+indent+" "),
indent)
}
// SetPeerMaj23 If a peer claims that it has 2/3 majority for given blockKey, call this.
// NOTE: if there are too many peers, or too much peer churn,
// this can cause memory issues.
// TODO: implement ability to remove peers too
func (hvs *HeightVoteSet) SetPeerMaj23(round int, voteType byte, peerID string, blockID *tmtypes.QbftBlockID) {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
if !IsVoteTypeValid(voteType) {
return
}
voteSet := hvs.getVoteSet(round, voteType)
if voteSet == nil {
return
}
voteSet.SetPeerMaj23(peerID, blockID)
}
// 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 (
"github.com/pkg/errors"
"encoding/json"
"github.com/33cn/chain33/common/crypto"
)
const (
maxBlockSizeBytes = 104857600 // 100MB
)
// ConsensusParams contains consensus critical parameters
// that determine the validity of blocks.
type ConsensusParams struct {
BlockSize `json:"block_size_params"`
TxSize `json:"tx_size_params"`
BlockGossip `json:"block_gossip_params"`
EvidenceParams `json:"evidence_params"`
}
// BlockSize contain limits on the block size.
type BlockSize struct {
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB
MaxTxs int `json:"max_txs"`
MaxGas int64 `json:"max_gas"`
}
// TxSize contain limits on the tx size.
type TxSize struct {
MaxBytes int `json:"max_bytes"`
MaxGas int64 `json:"max_gas"`
}
// BlockGossip determine consensus critical elements of how blocks are gossiped
type BlockGossip struct {
BlockPartSizeBytes int `json:"block_part_size_bytes"` // NOTE: must not be 0
}
// EvidenceParams determine how we handle evidence of malfeasance
type EvidenceParams struct {
MaxAge int64 `json:"max_age"` // only accept new evidence more recent than this
}
// DefaultConsensusParams returns a default ConsensusParams.
func DefaultConsensusParams() *ConsensusParams {
return &ConsensusParams{
DefaultBlockSize(),
DefaultTxSize(),
DefaultBlockGossip(),
DefaultEvidenceParams(),
}
}
// DefaultBlockSize returns a default BlockSize.
func DefaultBlockSize() BlockSize {
return BlockSize{
MaxBytes: 22020096, // 21MB
MaxTxs: 100000,
MaxGas: -1,
}
}
// DefaultTxSize returns a default TxSize.
func DefaultTxSize() TxSize {
return TxSize{
MaxBytes: 10240, // 10kB
MaxGas: -1,
}
}
// DefaultBlockGossip returns a default BlockGossip.
func DefaultBlockGossip() BlockGossip {
return BlockGossip{
BlockPartSizeBytes: 65536, // 64kB,
}
}
// DefaultEvidenceParams returns a default EvidenceParams.
func DefaultEvidenceParams() EvidenceParams {
return EvidenceParams{
MaxAge: 100000, // 27.8 hrs at 1block/s
}
}
// Validate validates the ConsensusParams to ensure all values
// are within their allowed limits, and returns an error if they are not.
func (params *ConsensusParams) Validate() error {
// ensure some values are greater than 0
if params.BlockSize.MaxBytes <= 0 {
return errors.Errorf("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
}
if params.BlockGossip.BlockPartSizeBytes <= 0 {
return errors.Errorf("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
}
// ensure blocks aren't too big
if params.BlockSize.MaxBytes > maxBlockSizeBytes {
return errors.Errorf("BlockSize.MaxBytes is too big. %d > %d",
params.BlockSize.MaxBytes, maxBlockSizeBytes)
}
return nil
}
// Hash returns a merkle hash of the parameters to store
// in the block header
func (params *ConsensusParams) Hash() []byte {
bytes, err := json.Marshal(params)
if err != nil {
ttlog.Error("block header Hash() marshal failed", "error", err)
return nil
}
return crypto.Ripemd160(bytes)
}
This diff is collapsed.
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 types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"time"
"github.com/33cn/chain33/common/crypto"
tmtypes "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// error defines
var (
ErrVoteUnexpectedStep = errors.New("Unexpected step")
ErrVoteInvalidValidatorIndex = errors.New("Invalid validator index")
ErrVoteInvalidValidatorAddress = errors.New("Invalid validator address")
ErrVoteInvalidSignature = errors.New("Invalid signature")
ErrVoteInvalidBlockHash = errors.New("Invalid block hash")
ErrVoteNonDeterministicSignature = errors.New("Non-deterministic signature")
ErrVoteConflict = errors.New("Conflicting vote")
ErrVoteNil = errors.New("Nil vote")
ErrAggVoteNil = errors.New("Nil aggregate vote")
)
// Signable is an interface for all signable things.
// It typically removes signatures before serializing.
type Signable interface {
WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
}
// SignBytes is a convenience method for getting the bytes to sign of a Signable.
func SignBytes(chainID string, o Signable) []byte {
buf, n, err := new(bytes.Buffer), new(int), new(error)
o.WriteSignBytes(chainID, buf, n, err)
if *err != nil {
PanicCrisis(err)
}
return buf.Bytes()
}
// Proposal defines a block proposal for the consensus.
// It refers to the block only by its PartSetHeader.
// It must be signed by the correct proposer for the given Height/Round
// to be considered valid. It may depend on votes from a previous round,
// a so-called Proof-of-Lock (POL) round, as noted in the POLRound and POLBlockID.
type Proposal struct {
tmtypes.QbftProposal
}
// NewProposal returns a new Proposal.
// If there is no POLRound, polRound should be -1.
func NewProposal(height int64, round int, blockhash []byte, polRound int, polBlockID tmtypes.QbftBlockID, seq int64) *Proposal {
return &Proposal{tmtypes.QbftProposal{
Height: height,
Round: int32(round),
Timestamp: time.Now().UnixNano(),
POLRound: int32(polRound),
POLBlockID: &polBlockID,
Blockhash: blockhash,
Sequence: seq,
},
}
}
// String returns a string representation of the Proposal.
func (p *Proposal) String() string {
return fmt.Sprintf("Proposal{%v/%v (%v, %X) %X %X %v @ %s}",
p.Height, p.Round, p.POLRound, p.POLBlockID.Hash,
p.Blockhash, p.Signature, p.Sequence, CanonicalTime(time.Unix(0, p.Timestamp)))
}
// WriteSignBytes writes the Proposal bytes for signing
func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
if *err != nil {
return
}
canonical := CanonicalJSONOnceProposal{
ChainID: chainID,
Proposal: CanonicalProposal(p),
}
byteOnceProposal, e := json.Marshal(&canonical)
if e != nil {
*err = e
return
}
number, writeErr := w.Write(byteOnceProposal)
*n = number
*err = writeErr
}
// Heartbeat ...
type Heartbeat struct {
*tmtypes.QbftHeartbeat
}
// WriteSignBytes writes the Heartbeat for signing.
// It panics if the Heartbeat is nil.
func (heartbeat *Heartbeat) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
if *err != nil {
return
}
canonical := CanonicalJSONOnceHeartbeat{
chainID,
CanonicalHeartbeat(heartbeat),
}
byteHeartbeat, e := json.Marshal(&canonical)
if e != nil {
*err = e
return
}
number, writeErr := w.Write(byteHeartbeat)
*n = number
*err = writeErr
}
// Types of votes
// TODO Make a new type "VoteType"
const (
VoteTypeNone = byte(0x0)
VoteTypePrevote = byte(0x01)
VoteTypePrecommit = byte(0x02)
)
// IsVoteTypeValid ...
func IsVoteTypeValid(voteType byte) bool {
switch voteType {
case VoteTypePrevote:
return true
case VoteTypePrecommit:
return true
default:
return false
}
}
// Vote Represents a prevote, precommit, or commit vote from validators for consensus.
type Vote struct {
*tmtypes.QbftVote
}
// WriteSignBytes ...
func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
if *err != nil {
return
}
canonical := CanonicalJSONOnceVote{
chainID,
CanonicalVote(vote),
}
byteVote, e := json.Marshal(&canonical)
if e != nil {
*err = e
ttlog.Error("vote WriteSignBytes marshal failed", "err", e)
return
}
number, writeErr := w.Write(byteVote)
*n = number
*err = writeErr
}
// Copy ...
func (vote *Vote) Copy() *Vote {
voteCopy := *vote
return &voteCopy
}
func (vote *Vote) String() string {
if vote == nil {
return "nil-Vote"
}
var typeString string
switch byte(vote.Type) {
case VoteTypePrevote:
typeString = "Prevote"
case VoteTypePrecommit:
typeString = "Precommit"
default:
PanicSanity("Unknown vote type")
}
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}",
vote.ValidatorIndex, Fingerprint(vote.ValidatorAddress),
vote.Height, vote.Round, vote.Type, typeString,
Fingerprint(vote.BlockID.Hash), vote.Signature,
CanonicalTime(time.Unix(0, vote.Timestamp)))
}
// Verify ...
func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
addr := GenAddressByPubKey(pubKey)
if !bytes.Equal(addr, vote.ValidatorAddress) {
return ErrVoteInvalidValidatorAddress
}
sig, err := ConsensusCrypto.SignatureFromBytes(vote.Signature)
if err != nil {
ttlog.Error("vote Verify fail", "err", err)
return err
}
if !pubKey.VerifyBytes(SignBytes(chainID, vote), sig) {
return ErrVoteInvalidSignature
}
return nil
}
// Hash ...
func (vote *Vote) Hash() []byte {
if vote == nil {
return nil
}
bytes, err := json.Marshal(vote)
if err != nil {
ttlog.Error("vote hash marshal failed", "err", err)
return nil
}
return crypto.Ripemd160(bytes)
}
// AggVote Represents a prevote, precommit, or commit vote from validators for consensus.
type AggVote struct {
*tmtypes.QbftAggVote
}
// WriteSignBytes ...
func (aggVote *AggVote) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
if *err != nil {
return
}
canonical := CanonicalJSONOnceAggVote{
chainID,
CanonicalAggVote(aggVote),
}
byteVote, e := json.Marshal(&canonical)
if e != nil {
*err = e
ttlog.Error("aggVote WriteSignBytes marshal failed", "err", e)
return
}
number, writeErr := w.Write(byteVote)
*n = number
*err = writeErr
}
// Verify ...
func (aggVote *AggVote) Verify(chainID string, valSet *ValidatorSet) error {
aggSig, err := ConsensusCrypto.SignatureFromBytes(aggVote.Signature)
if err != nil {
return errors.New("invalid aggregate signature")
}
pubs := make([]crypto.PubKey, 0)
arr := &BitArray{QbftBitArray: aggVote.ValidatorArray}
for i, val := range valSet.Validators {
if arr.GetIndex(i) {
pub, _ := ConsensusCrypto.PubKeyFromBytes(val.PubKey)
pubs = append(pubs, pub)
}
}
origVote := &Vote{&tmtypes.QbftVote{
BlockID: aggVote.BlockID,
Height: aggVote.Height,
Round: aggVote.Round,
Timestamp: aggVote.Timestamp,
Type: aggVote.Type,
UseAggSig: true,
}}
aggr, err := crypto.ToAggregate(ConsensusCrypto)
if err != nil {
return err
}
err = aggr.VerifyAggregatedOne(pubs, SignBytes(chainID, origVote), aggSig)
if err != nil {
ttlog.Error("aggVote Verify fail", "err", err, "aggVote", aggVote, "aggSig", aggSig)
return err
}
return nil
}
// Copy ...
func (aggVote *AggVote) Copy() *AggVote {
copy := *aggVote
return &copy
}
func (aggVote *AggVote) String() string {
if aggVote == nil {
return "nil-AggVote"
}
var typeString string
switch byte(aggVote.Type) {
case VoteTypePrevote:
typeString = "Prevote"
case VoteTypePrecommit:
typeString = "Precommit"
default:
PanicSanity("Unknown vote type")
}
bitArray := &BitArray{QbftBitArray: aggVote.ValidatorArray}
return fmt.Sprintf("AggVote{%X %v/%02d/%v(%v) %X %X @ %s %v}",
Fingerprint(aggVote.ValidatorAddress),
aggVote.Height, aggVote.Round, aggVote.Type, typeString,
Fingerprint(aggVote.BlockID.Hash), aggVote.Signature,
CanonicalTime(time.Unix(0, aggVote.Timestamp)),
bitArray)
}
// Hash ...
func (aggVote *AggVote) Hash() []byte {
if aggVote == nil {
return nil
}
bytes, err := json.Marshal(aggVote)
if err != nil {
ttlog.Error("aggVote hash marshal failed", "err", err)
return nil
}
return crypto.Ripemd160(bytes)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
_ "github.com/33cn/plugin/plugin/dapp/paracross" //auto gen _ "github.com/33cn/plugin/plugin/dapp/paracross" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/pokerbull" //auto gen _ "github.com/33cn/plugin/plugin/dapp/pokerbull" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/privacy" //auto gen _ "github.com/33cn/plugin/plugin/dapp/privacy" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/qbftNode" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/relay" //auto gen _ "github.com/33cn/plugin/plugin/dapp/relay" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/retrieve" //auto gen _ "github.com/33cn/plugin/plugin/dapp/retrieve" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/storage" //auto gen _ "github.com/33cn/plugin/plugin/dapp/storage" //auto gen
......
// 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 commands
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"os"
"strconv"
"time"
"github.com/33cn/chain33/common/crypto"
"github.com/33cn/chain33/rpc/jsonclient"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
ttypes "github.com/33cn/plugin/plugin/consensus/qbft/types"
vt "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
"github.com/spf13/cobra"
)
var (
strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters
genFile = "genesis_file.json"
pvFile = "priv_validator_"
//AuthBLS ...
AuthBLS = 259
)
// ValCmd qbftNode cmd register
func ValCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "qbft",
Short: "Construct qbft transactions",
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(
IsSyncCmd(),
GetBlockInfoCmd(),
GetNodeInfoCmd(),
GetCurrentStateCmd(),
GetPerfStatCmd(),
AddNodeCmd(),
CreateCmd(),
)
return cmd
}
// IsSyncCmd query qbft is sync
func IsSyncCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "is_sync",
Short: "Query qbft consensus is sync",
Run: isSync,
}
return cmd
}
func isSync(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
var res bool
ctx := jsonclient.NewRPCCtx(rpcLaddr, "qbftNode.IsSync", nil, &res)
ctx.Run()
}
// GetNodeInfoCmd get validator nodes
func GetNodeInfoCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "nodes",
Short: "Get qbft validator nodes",
Run: getNodeInfo,
}
return cmd
}
func getNodeInfo(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
var res *vt.QbftNodeInfoSet
ctx := jsonclient.NewRPCCtx(rpcLaddr, "qbftNode.GetNodeInfo", nil, &res)
ctx.Run()
}
// GetBlockInfoCmd get block info
func GetBlockInfoCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "info",
Short: "Get qbft consensus info",
Run: getBlockInfo,
}
addGetBlockInfoFlags(cmd)
return cmd
}
func addGetBlockInfoFlags(cmd *cobra.Command) {
cmd.Flags().Int64P("height", "t", 0, "block height (larger than 0)")
cmd.MarkFlagRequired("height")
}
func getBlockInfo(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
height, _ := cmd.Flags().GetInt64("height")
req := &vt.ReqQbftBlockInfo{
Height: height,
}
params := rpctypes.Query4Jrpc{
Execer: vt.QbftNodeX,
FuncName: "GetBlockInfoByHeight",
Payload: types.MustPBToJSON(req),
}
var res vt.QbftBlockInfo
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.Query", params, &res)
ctx.SetResultCb(jsonOutput)
result, err := ctx.RunResult()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(result)
}
func jsonOutput(arg interface{}) (interface{}, error) {
res := arg.(types.Message)
data, err := types.PBToJSON(res)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = json.Indent(&buf, data, "", " ")
if err != nil {
return nil, err
}
return buf.String(), nil
}
// GetCurrentStateCmd get current consensus state
func GetCurrentStateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "state",
Short: "Get qbft current consensus state",
Run: getCurrentState,
}
return cmd
}
func getCurrentState(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
params := rpctypes.Query4Jrpc{
Execer: vt.QbftNodeX,
FuncName: "GetCurrentState",
}
var res vt.QbftState
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.Query", params, &res)
ctx.SetResultCb(jsonOutput)
result, err := ctx.RunResult()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(result)
}
// GetPerfStatCmd get block info
func GetPerfStatCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "perf_stat",
Short: "Get qbft performance statistics",
Run: getPerfStat,
}
addGetPerfStatFlags(cmd)
return cmd
}
func addGetPerfStatFlags(cmd *cobra.Command) {
cmd.Flags().Int64P("start", "s", 0, "start block height")
cmd.Flags().Int64P("end", "e", 0, "end block height")
}
func getPerfStat(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
start, _ := cmd.Flags().GetInt64("start")
end, _ := cmd.Flags().GetInt64("end")
req := &vt.ReqQbftPerfStat{
Start: start,
End: end,
}
params := rpctypes.Query4Jrpc{
Execer: vt.QbftNodeX,
FuncName: "GetPerfStat",
Payload: types.MustPBToJSON(req),
}
var res vt.QbftPerfStat
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.Query", params, &res)
ctx.Run()
}
// AddNodeCmd add validator node
func AddNodeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "Add qbft validator node",
Run: addNode,
}
addNodeFlags(cmd)
return cmd
}
func addNodeFlags(cmd *cobra.Command) {
cmd.Flags().StringP("pubkey", "p", "", "public key")
cmd.MarkFlagRequired("pubkey")
cmd.Flags().Int64P("power", "w", 0, "voting power")
cmd.MarkFlagRequired("power")
}
func addNode(cmd *cobra.Command, args []string) {
title, _ := cmd.Flags().GetString("title")
cfg := types.GetCliSysParam(title)
pubkey, _ := cmd.Flags().GetString("pubkey")
power, _ := cmd.Flags().GetInt64("power")
value := &vt.QbftNodeAction_Node{Node: &vt.QbftNode{PubKey: pubkey, Power: power}}
action := &vt.QbftNodeAction{Value: value, Ty: vt.QbftNodeActionUpdate}
tx := &types.Transaction{Payload: types.Encode(action)}
tx, err := types.FormatTx(cfg, vt.QbftNodeX, tx)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
txHex := types.Encode(tx)
fmt.Println(hex.EncodeToString(txHex))
}
//CreateCmd to create keyfiles
func CreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "gen_file",
Short: "Generate qbft genesis and priv file",
Run: createFiles,
}
addCreateCmdFlags(cmd)
return cmd
}
func addCreateCmdFlags(cmd *cobra.Command) {
cmd.Flags().StringP("num", "n", "", "num of the keyfile to create")
cmd.MarkFlagRequired("num")
cmd.Flags().StringP("type", "t", "ed25519", "sign type of the keyfile (secp256k1, ed25519, sm2, bls)")
}
// RandStr ...
func RandStr(length int) string {
chars := []byte{}
MAIN_LOOP:
for {
val := rand.Int63()
for i := 0; i < 10; i++ {
v := int(val & 0x3f) // rightmost 6 bits
if v >= 62 { // only 62 characters in strChars
val >>= 6
continue
} else {
chars = append(chars, strChars[v])
if len(chars) == length {
break MAIN_LOOP
}
val >>= 6
}
}
}
return string(chars)
}
func initCryptoImpl(signType int) error {
ttypes.CryptoName = types.GetSignName("", signType)
cr, err := crypto.New(ttypes.CryptoName)
if err != nil {
fmt.Printf("init crypto fail: %v", err)
return err
}
ttypes.ConsensusCrypto = cr
return nil
}
func createFiles(cmd *cobra.Command, args []string) {
// init crypto instance
ty, _ := cmd.Flags().GetString("type")
signType, ok := ttypes.SignMap[ty]
if !ok {
fmt.Println("type parameter is not valid")
return
}
err := initCryptoImpl(signType)
if err != nil {
return
}
// genesis file
genDoc := ttypes.GenesisDoc{
ChainID: fmt.Sprintf("chain33-%v", RandStr(6)),
GenesisTime: time.Now(),
}
num, _ := cmd.Flags().GetString("num")
n, err := strconv.Atoi(num)
if err != nil {
fmt.Println("num parameter is not valid digit")
return
}
for i := 0; i < n; i++ {
// create private validator file
pvFileName := pvFile + strconv.Itoa(i) + ".json"
privValidator := ttypes.LoadOrGenPrivValidatorFS(pvFileName)
if privValidator == nil {
fmt.Println("create priv_validator file fail")
break
}
// create genesis validator by the pubkey of private validator
gv := ttypes.GenesisValidator{
PubKey: ttypes.KeyText{Kind: ttypes.CryptoName, Data: privValidator.GetPubKey().KeyString()},
Power: 10,
}
genDoc.Validators = append(genDoc.Validators, gv)
}
if err := genDoc.SaveAs(genFile); err != nil {
fmt.Println("generate genesis file fail")
return
}
fmt.Printf("generate genesis file path %v\n", genFile)
}
// 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 (
"errors"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
const managerKey = "qbft-manager"
// Exec_Node method
func (val *QbftNode) Exec_Node(node *pty.QbftNode, tx *types.Transaction, index int) (*types.Receipt, error) {
if !isValidManager(tx.From(), val.GetStateDB()) {
return nil, errors.New("not valid manager")
}
if node.GetPubKey() == "" {
return nil, errors.New("validator pubkey is empty")
}
if node.GetPower() < 0 {
return nil, errors.New("validator power must not be negative")
}
receipt := &types.Receipt{Ty: types.ExecOk, KV: nil, Logs: nil}
return receipt, nil
}
// Exec_BlockInfo method
func (val *QbftNode) Exec_BlockInfo(blockInfo *pty.QbftBlockInfo, tx *types.Transaction, index int) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk, KV: nil, Logs: nil}
return receipt, nil
}
func getConfigKey(key string, db dbm.KV) ([]byte, error) {
configKey := types.ConfigKey(key)
value, err := db.Get([]byte(configKey))
if err != nil {
clog.Info("getConfigKey not find", "configKey", configKey, "err", err)
return nil, err
}
return value, nil
}
func getManageKey(key string, db dbm.KV) ([]byte, error) {
manageKey := types.ManageKey(key)
value, err := db.Get([]byte(manageKey))
if err != nil {
clog.Info("getManageKey not find", "manageKey", manageKey, "err", err)
return getConfigKey(key, db)
}
return value, nil
}
func isValidManager(addr string, db dbm.KV) bool {
value, err := getManageKey(managerKey, db)
if err != nil {
clog.Error("isValidManager nil key", "managerKey", managerKey)
return false
}
if value == nil {
clog.Error("isValidManager nil value")
return false
}
var item types.ConfigItem
err = types.Decode(value, &item)
if err != nil {
clog.Error("isValidManager decode fail", "err", err)
return false
}
for _, op := range item.GetArr().Value {
if op == addr {
return true
}
}
return false
}
// 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"
pty "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// ExecDelLocal_Node method
func (val *QbftNode) ExecDelLocal_Node(node *pty.QbftNode, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
set := &types.LocalDBSet{}
key := CalcQbftNodeUpdateHeightIndexKey(val.GetHeight(), index)
set.KV = append(set.KV, &types.KeyValue{Key: key, Value: nil})
return set, nil
}
// ExecDelLocal_BlockInfo method
func (val *QbftNode) ExecDelLocal_BlockInfo(blockInfo *pty.QbftBlockInfo, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
set := &types.LocalDBSet{}
key := CalcQbftNodeBlockInfoHeightKey(val.GetHeight())
set.KV = append(set.KV, &types.KeyValue{Key: key, Value: nil})
return set, nil
}
// 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"
pty "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// ExecLocal_Node method
func (val *QbftNode) ExecLocal_Node(node *pty.QbftNode, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
set := &types.LocalDBSet{}
clog.Info("update validator", "pubkey", node.GetPubKey(), "power", node.GetPower())
key := CalcQbftNodeUpdateHeightIndexKey(val.GetHeight(), index)
set.KV = append(set.KV, &types.KeyValue{Key: key, Value: types.Encode(node)})
return set, nil
}
// ExecLocal_BlockInfo method
func (val *QbftNode) ExecLocal_BlockInfo(blockInfo *pty.QbftBlockInfo, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
set := &types.LocalDBSet{}
key := CalcQbftNodeBlockInfoHeightKey(val.GetHeight())
set.KV = append(set.KV, &types.KeyValue{Key: key, Value: types.Encode(blockInfo)})
return set, nil
}
// 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"
log "github.com/33cn/chain33/common/log/log15"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
)
var clog = log.New("module", "execs.qbftNode")
var driverName = "qbftNode"
// Init method
func Init(name string, cfg *types.Chain33Config, sub []byte) {
clog.Debug("register qbftNode execer")
drivers.Register(cfg, GetName(), newQbftNode, cfg.GetDappFork(driverName, "Enable"))
InitExecType()
}
//InitExecType ...
func InitExecType() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&QbftNode{}))
}
// GetName method
func GetName() string {
return newQbftNode().GetName()
}
// QbftNode strucyt
type QbftNode struct {
drivers.DriverBase
}
func newQbftNode() drivers.Driver {
n := &QbftNode{}
n.SetChild(n)
n.SetIsFree(true)
n.SetExecutorType(types.LoadExecutorType(driverName))
return n
}
// GetDriverName method
func (qbft *QbftNode) GetDriverName() string {
return driverName
}
// CheckTx method
func (qbft *QbftNode) CheckTx(tx *types.Transaction, index int) error {
return nil
}
// CalcQbftNodeUpdateHeightIndexKey method
func CalcQbftNodeUpdateHeightIndexKey(height int64, index int) []byte {
return []byte(fmt.Sprintf("LODB-qbftNode-Update:%18d:%18d", height, int64(index)))
}
// CalcQbftNodeUpdateHeightKey method
func CalcQbftNodeUpdateHeightKey(height int64) []byte {
return []byte(fmt.Sprintf("LODB-qbftNode-Update:%18d:", height))
}
// CalcQbftNodeBlockInfoHeightKey method
func CalcQbftNodeBlockInfoHeightKey(height int64) []byte {
return []byte(fmt.Sprintf("LODB-qbftNode-BlockInfo:%18d:", height))
}
// CheckReceiptExecOk return true to check if receipt ty is ok
func (qbft *QbftNode) CheckReceiptExecOk() bool {
return true
}
// 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"
pty "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// Query_GetQbftNodeByHeight method
func (val *QbftNode) Query_GetQbftNodeByHeight(in *pty.ReqQbftNodes) (types.Message, error) {
height := in.GetHeight()
if height <= 0 {
return nil, types.ErrInvalidParam
}
key := CalcQbftNodeUpdateHeightKey(height)
values, err := val.GetLocalDB().List(key, nil, 0, 1)
if err != nil {
return nil, err
}
if len(values) == 0 {
return nil, types.ErrNotFound
}
reply := &pty.QbftNodes{}
for _, qbftNodeByte := range values {
var qbftNode pty.QbftNode
err := types.Decode(qbftNodeByte, &qbftNode)
if err != nil {
return nil, err
}
reply.Nodes = append(reply.Nodes, &qbftNode)
}
return reply, nil
}
// Query_GetBlockInfoByHeight method
func (val *QbftNode) Query_GetBlockInfoByHeight(in *pty.ReqQbftBlockInfo) (types.Message, error) {
height := in.GetHeight()
if height <= 0 {
return nil, types.ErrInvalidParam
}
key := CalcQbftNodeBlockInfoHeightKey(height)
value, err := val.GetLocalDB().Get(key)
if err != nil {
return nil, err
}
if len(value) == 0 {
return nil, types.ErrNotFound
}
reply := &pty.QbftBlockInfo{}
err = types.Decode(value, reply)
if err != nil {
return nil, err
}
return reply, nil
}
// Query_GetCurrentState method
func (val *QbftNode) Query_GetCurrentState(in *types.ReqNil) (types.Message, error) {
return val.GetAPI().QueryConsensusFunc("qbft", "CurrentState", &types.ReqNil{})
}
// Query_GetPerfState method
func (val *QbftNode) Query_GetPerfStat(in *pty.ReqQbftPerfStat) (types.Message, error) {
start := in.GetStart()
end := in.GetEnd()
if start < 0 || end < 0 || start > end || end > val.GetHeight() {
return nil, types.ErrInvalidParam
}
if start == 0 {
start = 1
}
if end == 0 {
end = val.GetHeight()
}
startKey := CalcQbftNodeBlockInfoHeightKey(start)
startValue, err := val.GetLocalDB().Get(startKey)
if err != nil {
return nil, err
}
if len(startValue) == 0 {
return nil, types.ErrNotFound
}
startInfo := &pty.QbftBlockInfo{}
err = types.Decode(startValue, startInfo)
if err != nil {
return nil, err
}
endKey := CalcQbftNodeBlockInfoHeightKey(end)
endValue, err := val.GetLocalDB().Get(endKey)
if err != nil {
return nil, err
}
if len(endValue) == 0 {
return nil, types.ErrNotFound
}
endInfo := &pty.QbftBlockInfo{}
err = types.Decode(endValue, endInfo)
if err != nil {
return nil, err
}
startHeader := startInfo.Block.Header
endHeader := endInfo.Block.Header
totalTx := endHeader.TotalTxs - startHeader.TotalTxs
totalBlock := endHeader.Height - startHeader.Height + 1
totalSecond := endHeader.Time - startHeader.Time + 1
return &pty.QbftPerfStat{
TotalTx: totalTx,
TotalBlock: totalBlock,
TxPerBlock: totalTx / totalBlock,
TotalSecond: totalSecond,
TxPerSecond: totalTx / totalSecond,
}, nil
}
// 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 qbftNode
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/qbftNode/commands"
"github.com/33cn/plugin/plugin/dapp/qbftNode/executor"
"github.com/33cn/plugin/plugin/dapp/qbftNode/rpc"
"github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: types.QbftNodeX,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: commands.ValCmd,
RPC: rpc.Init,
})
}
all:
sh ./create_protobuf.sh
#!/bin/sh
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";
import "blockchain.proto";
package types;
message QbftBlockID {
bytes hash = 1;
}
message QbftBitArray {
int32 bits = 1;
repeated uint64 elems = 2;
}
message QbftVote {
bytes validatorAddress = 1;
int32 validatorIndex = 2;
int64 height = 3;
int32 round = 4;
int64 timestamp = 5;
uint32 type = 6;
QbftBlockID blockID = 7;
bytes signature = 8;
bool useAggSig = 9;
}
message QbftCommit {
QbftBlockID blockID = 1;
repeated QbftVote prevotes = 2;
repeated QbftVote precommits = 3;
QbftAggVote aggVote = 4;
uint32 voteType = 5;
}
message QbftBlockInfo {
QbftState state = 1;
QbftProposal proposal = 2;
QbftBlock block = 3;
}
message QbftBlockSize {
int32 maxBytes = 1;
int32 maxTxs = 2;
int64 maxGas = 3;
}
message QbftTxSize {
int32 maxBytes = 1;
int64 maxGas = 2;
}
message QbftBlockGossip {
int32 blockPartSizeBytes = 1;
}
message QbftEvidenceParams {
int64 maxAge = 1;
}
message QbftConsensusParams {
QbftBlockSize blockSize = 1;
QbftTxSize txSize = 2;
QbftBlockGossip blockGossip = 3;
QbftEvidenceParams evidenceParams = 4;
}
message QbftValidator {
string address = 1;
string pubKey = 2;
int64 votingPower = 3;
int64 accum = 4;
}
message QbftValidatorSet {
repeated QbftValidator validators = 1;
QbftValidator proposer = 2;
}
message QbftState {
string chainID = 1;
int64 lastBlockHeight = 2;
int64 lastBlockTotalTx = 3;
QbftBlockID lastBlockID = 4;
int64 lastBlockTime = 5;
QbftValidatorSet validators = 6;
QbftValidatorSet lastValidators = 7;
int64 lastHeightValidatorsChanged = 8;
QbftConsensusParams consensusParams = 9;
int64 lastHeightConsensusParamsChanged = 10;
bytes lastResultsHash = 11;
bytes appHash = 12;
int64 sequence = 13;
int64 lastSequence = 14;
int64 lastCommitRound = 15;
}
message QbftBlockHeader {
string chainID = 1;
int64 height = 2;
int64 round = 3;
int64 time = 4;
int64 numTxs = 5;
QbftBlockID lastBlockID = 6;
int64 totalTxs = 7;
bytes lastCommitHash = 8;
bytes validatorsHash = 9;
bytes consensusHash = 10;
bytes appHash = 11;
bytes lastResultsHash = 12;
bytes proposerAddr = 13;
int64 sequence = 14;
int64 lastSequence = 15;
}
message QbftBlock {
QbftBlockHeader header = 1;
Block data = 2;
QbftCommit lastCommit = 4;
}
message QbftProposal {
int64 height = 1;
int32 round = 2;
int64 timestamp = 3;
int32 POLRound = 4;
QbftBlockID POLBlockID = 5;
bytes signature = 6;
bytes blockhash = 7;
int64 sequence = 8;
}
message QbftNewRoundStepMsg {
int64 height = 1;
int32 round = 2;
int32 step = 3;
int32 secondsSinceStartTime = 4;
int32 lastCommitRound = 5;
}
message QbftValidBlockMsg {
int64 height = 1;
int32 round = 2;
bytes blockhash = 3;
bool isCommit = 4;
}
message QbftProposalPOLMsg {
int64 height = 1;
int32 proposalPOLRound = 2;
QbftBitArray proposalPOL = 3;
}
message QbftHasVoteMsg {
int64 height = 1;
int32 round = 2;
int32 type = 3;
int32 index = 4;
}
message QbftVoteSetMaj23Msg {
int64 height = 1;
int32 round = 2;
int32 type = 3;
QbftBlockID blockID = 4;
}
message QbftVoteSetBitsMsg {
int64 height = 1;
int32 round = 2;
int32 type = 3;
QbftBlockID blockID = 4;
QbftBitArray votes = 5;
}
message QbftHeartbeat {
bytes validatorAddress = 1;
int32 validatorIndex = 2;
int64 height = 3;
int32 round = 4;
int32 sequence = 5;
bytes signature = 6;
}
message QbftIsHealthy {
bool isHealthy = 1;
}
message QbftAggVote {
bytes validatorAddress = 1;
QbftBitArray validatorArray = 2;
int64 height = 3;
int32 round = 4;
int64 timestamp = 5;
uint32 type = 6;
QbftBlockID blockID = 7;
bytes signature = 8;
}
\ No newline at end of file
syntax = "proto3";
package types;
import "common.proto";
import "qbft.proto";
message QbftNode {
string pubKey = 1;
int64 power = 2;
}
message QbftNodes {
repeated QbftNode nodes = 1;
}
message QbftNodeAction {
oneof value {
QbftNode node = 1;
QbftBlockInfo blockInfo = 2;
}
int32 Ty = 3;
}
message ReqQbftNodes {
int64 height = 1;
}
message ReqQbftBlockInfo {
int64 height = 1;
}
message QbftNodeInfo {
string nodeIP = 1;
string nodeID = 2;
string address = 3;
string pubKey = 4;
int64 votingPower = 5;
int64 accum = 6;
}
message QbftNodeInfoSet {
repeated QbftNodeInfo nodes = 1;
}
message QbftPerfStat {
int64 totalTx = 1;
int64 totalBlock = 2;
int64 txPerBlock = 3;
int64 totalSecond = 4;
int64 txPerSecond = 5;
}
message ReqQbftPerfStat {
int64 start = 1;
int64 end = 2;
}
service qbftNode {
rpc IsSync(ReqNil) returns (QbftIsHealthy) {}
rpc GetNodeInfo(ReqNil) returns (QbftNodeInfoSet) {}
}
\ No newline at end of file
// 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
import (
"context"
"github.com/33cn/chain33/types"
vt "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// IsSync query is sync
func (c *channelClient) IsSync(ctx context.Context, req *types.ReqNil) (*vt.QbftIsHealthy, error) {
data, err := c.QueryConsensusFunc("qbft", "IsHealthy", &types.ReqNil{})
if err != nil {
return nil, err
}
if resp, ok := data.(*vt.QbftIsHealthy); ok {
return resp, nil
}
return nil, types.ErrDecode
}
// IsSync query is sync
func (c *Jrpc) IsSync(req *types.ReqNil, result *interface{}) error {
data, err := c.cli.IsSync(context.Background(), req)
if err != nil {
return err
}
*result = data.IsHealthy
return nil
}
// GetNodeInfo query node info
func (c *channelClient) GetNodeInfo(ctx context.Context, req *types.ReqNil) (*vt.QbftNodeInfoSet, error) {
data, err := c.QueryConsensusFunc("qbft", "NodeInfo", &types.ReqNil{})
if err != nil {
return nil, err
}
if resp, ok := data.(*vt.QbftNodeInfoSet); ok {
return resp, nil
}
return nil, types.ErrDecode
}
// GetNodeInfo query node info
func (c *Jrpc) GetNodeInfo(req *types.ReqNil, result *interface{}) error {
data, err := c.cli.GetNodeInfo(context.Background(), req)
if err != nil {
return err
}
*result = data
return nil
}
/*
* 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
//only load all plugin and system
import (
"testing"
"strings"
"github.com/33cn/chain33/client"
"github.com/33cn/chain33/client/mocks"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
vt "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"golang.org/x/net/context"
)
func newGrpc(api client.QueueProtocolAPI) *channelClient {
return &channelClient{
ChannelClient: rpctypes.ChannelClient{QueueProtocolAPI: api},
}
}
func newJrpc(api client.QueueProtocolAPI) *Jrpc {
return &Jrpc{cli: newGrpc(api)}
}
func TestChannelClient_IsSync(t *testing.T) {
cfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1))
api := new(mocks.QueueProtocolAPI)
api.On("GetConfig", mock.Anything).Return(cfg, nil)
client := newGrpc(api)
client.Init("qbftNode", nil, nil, nil)
req := &types.ReqNil{}
api.On("QueryConsensusFunc", "qbft", "IsHealthy", req).Return(&vt.QbftIsHealthy{IsHealthy: true}, nil)
result, err := client.IsSync(context.Background(), req)
assert.Nil(t, err)
assert.Equal(t, true, result.IsHealthy)
}
func TestJrpc_IsSync(t *testing.T) {
api := new(mocks.QueueProtocolAPI)
J := newJrpc(api)
req := &types.ReqNil{}
var result interface{}
api.On("QueryConsensusFunc", "qbft", "IsHealthy", req).Return(&vt.QbftIsHealthy{IsHealthy: true}, nil)
err := J.IsSync(req, &result)
assert.Nil(t, err)
assert.Equal(t, true, result)
}
func TestChannelClient_GetNodeInfo(t *testing.T) {
cfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1))
api := new(mocks.QueueProtocolAPI)
api.On("GetConfig", mock.Anything).Return(cfg, nil)
client := newGrpc(api)
client.Init("qbftNode", nil, nil, nil)
req := &types.ReqNil{}
node := &vt.QbftNodeInfo{
NodeIP: "127.0.0.1",
NodeID: "001",
Address: "aaa",
PubKey: "bbb",
VotingPower: 10,
Accum: -1,
}
set := &vt.QbftNodeInfoSet{
Nodes: []*vt.QbftNodeInfo{node},
}
api.On("QueryConsensusFunc", "qbft", "NodeInfo", req).Return(set, nil)
result, err := client.GetNodeInfo(context.Background(), req)
assert.Nil(t, err)
assert.EqualValues(t, set, result)
}
func TestJrpc_GetNodeInfo(t *testing.T) {
api := new(mocks.QueueProtocolAPI)
J := newJrpc(api)
req := &types.ReqNil{}
var result interface{}
node := &vt.QbftNodeInfo{
NodeIP: "127.0.0.1",
NodeID: "001",
Address: "aaa",
PubKey: "bbb",
VotingPower: 10,
Accum: -1,
}
set := &vt.QbftNodeInfoSet{
Nodes: []*vt.QbftNodeInfo{node},
}
api.On("QueryConsensusFunc", "qbft", "NodeInfo", req).Return(set, nil)
err := J.GetNodeInfo(req, &result)
assert.Nil(t, err)
assert.EqualValues(t, set, result)
}
// 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
import (
"github.com/33cn/chain33/rpc/types"
vt "github.com/33cn/plugin/plugin/dapp/qbftNode/types"
)
// Jrpc qbftNode jrpc interface
type Jrpc struct {
cli *channelClient
}
// Grpc qbftNode Grpc interface
type Grpc struct {
*channelClient
}
type channelClient struct {
types.ChannelClient
}
// Init qbftNode rpc register
func Init(name string, s types.RPCServer) {
cli := &channelClient{}
grpc := &Grpc{channelClient: cli}
cli.Init(name, s, &Jrpc{cli: cli}, grpc)
vt.RegisterQbftNodeServer(s.GRPC(), grpc)
}
// 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
// QbftNodeX define
const QbftNodeX = "qbftNode"
// qbftNode action
const (
QbftNodeActionUpdate = 1
QbftNodeActionBlockInfo = 2
)
// action name
const (
ActionNodeUpdate = "NodeUpdate"
)
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 types
import (
"encoding/json"
"github.com/33cn/chain33/common/address"
log "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
var tlog = log.New("module", "exectype."+QbftNodeX)
func init() {
types.AllowUserExec = append(types.AllowUserExec, []byte(QbftNodeX))
types.RegFork(QbftNodeX, InitFork)
types.RegExec(QbftNodeX, InitExecutor)
}
//InitFork ...
func InitFork(cfg *types.Chain33Config) {
cfg.RegisterDappFork(QbftNodeX, "Enable", 0)
}
//InitExecutor ...
func InitExecutor(cfg *types.Chain33Config) {
types.RegistorExecutor(QbftNodeX, NewType(cfg))
}
// QbftNodeType stuct
type QbftNodeType struct {
types.ExecTypeBase
}
// NewType method
func NewType(cfg *types.Chain33Config) *QbftNodeType {
c := &QbftNodeType{}
c.SetChild(c)
c.SetConfig(cfg)
return c
}
// GetName 获取执行器名称
func (t *QbftNodeType) GetName() string {
return QbftNodeX
}
// GetPayload method
func (t *QbftNodeType) GetPayload() types.Message {
return &QbftNodeAction{}
}
// GetTypeMap method
func (t *QbftNodeType) GetTypeMap() map[string]int32 {
return map[string]int32{
"Node": QbftNodeActionUpdate,
"BlockInfo": QbftNodeActionBlockInfo,
}
}
// GetLogMap method
func (t *QbftNodeType) GetLogMap() map[int64]*types.LogInfo {
return map[int64]*types.LogInfo{}
}
// CreateTx ...
func (t *QbftNodeType) CreateTx(action string, message json.RawMessage) (*types.Transaction, error) {
tlog.Debug("qbftNode.CreateTx", "action", action)
cfg := t.GetConfig()
if action == ActionNodeUpdate {
var param NodeUpdateTx
err := json.Unmarshal(message, &param)
if err != nil {
tlog.Error("qbftNode.CreateTx", "err", err)
return nil, types.ErrInvalidParam
}
return CreateNodeUpdateTx(cfg, &param)
}
return nil, types.ErrNotSupport
}
// CreateNodeUpdateTx ...
func CreateNodeUpdateTx(cfg *types.Chain33Config, parm *NodeUpdateTx) (*types.Transaction, error) {
if parm == nil {
tlog.Error("CreateNodeUpdateTx", "parm", parm)
return nil, types.ErrInvalidParam
}
v := &QbftNode{
PubKey: parm.PubKey,
Power: parm.Power,
}
update := &QbftNodeAction{
Ty: QbftNodeActionUpdate,
Value: &QbftNodeAction_Node{v},
}
execName := cfg.ExecName(QbftNodeX)
tx := &types.Transaction{
Execer: []byte(execName),
Payload: types.Encode(update),
To: address.ExecAddress(execName),
}
tx, err := types.FormatTx(cfg, execName, tx)
if err != nil {
return nil, err
}
return tx, nil
}
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 types
// NodeUpdateTx for construction
type NodeUpdateTx struct {
PubKey string `json:"pubKey"`
Power int64 `json:"power"`
}
...@@ -253,3 +253,5 @@ ForkParaAssetTransferRbk=0 ...@@ -253,3 +253,5 @@ ForkParaAssetTransferRbk=0
#仅平行链适用,开启挖矿交易的高度,已有代码版本可能未在0高度开启挖矿,需要设置这个高度,新版本默认从0开启挖矿,通过交易配置分阶段奖励 #仅平行链适用,开启挖矿交易的高度,已有代码版本可能未在0高度开启挖矿,需要设置这个高度,新版本默认从0开启挖矿,通过交易配置分阶段奖励
ForkParaFullMinerHeight=0 ForkParaFullMinerHeight=0
[fork.sub.qbftNode]
Enable=0
...@@ -410,6 +410,9 @@ ForkIssuanceTableUpdate=0 ...@@ -410,6 +410,9 @@ ForkIssuanceTableUpdate=0
Enable=0 Enable=0
ForkCollateralizeTableUpdate=0 ForkCollateralizeTableUpdate=0
[fork.sub.qbftNode]
Enable=0
#对已有的平行链如果不是从0开始同步数据,需要设置这个kvmvccmavl的对应平行链高度的fork,如果从0开始同步,statehash会跟以前mavl的不同 #对已有的平行链如果不是从0开始同步数据,需要设置这个kvmvccmavl的对应平行链高度的fork,如果从0开始同步,statehash会跟以前mavl的不同
[fork.sub.store-kvmvccmavl] [fork.sub.store-kvmvccmavl]
ForkKvmvccmavl=1 ForkKvmvccmavl=1
......
...@@ -165,9 +165,21 @@ func (a *action) commitVote(commit *vty.CommitVote) (*types.Receipt, error) { ...@@ -165,9 +165,21 @@ func (a *action) commitVote(commit *vty.CommitVote) (*types.Receipt, error) {
receipt := &types.Receipt{Ty: types.ExecOk} receipt := &types.Receipt{Ty: types.ExecOk}
vote, err := a.getVoteInfo(commit.VoteID) vote, err := a.getVoteInfo(commit.VoteID)
if err != nil { if err != nil {
elog.Error("vote exec commitVote", "txHash", a.txHash, "get vote err", err) elog.Error("vote exec commitVote", "txHash", a.txHash, "vid", commit.VoteID, "get vote err", err)
return nil, errStateDBGet return nil, errStateDBGet
} }
// 由于目前底层检测交易阶段提供区块时间可能不是最新区块的,依赖区块时间的比较需要放在执行阶段处理
if vote.BeginTimestamp > a.blockTime {
elog.Error("vote exec commitVote", "txHash", a.txHash, "vid", commit.VoteID,
"beginTime", vote.BeginTimestamp, "blockTime", a.blockTime, "err", errVoteNotStarted)
return nil, errVoteNotStarted
}
if vote.EndTimestamp <= a.blockTime {
elog.Error("vote exec commitVote", "txHash", a.txHash, "vid", commit.VoteID,
"endTime", vote.EndTimestamp, "blockTime", a.blockTime, "err", errVoteAlreadyFinished)
return nil, errVoteAlreadyFinished
}
group, err := a.getGroupInfo(vote.GroupID) group, err := a.getGroupInfo(vote.GroupID)
if err != nil { if err != nil {
elog.Error("vote exec commitVote", "txHash", a.txHash, "get group err", err) elog.Error("vote exec commitVote", "txHash", a.txHash, "get group err", err)
......
...@@ -37,7 +37,7 @@ func (v *vote) CheckTx(tx *types.Transaction, index int) error { ...@@ -37,7 +37,7 @@ func (v *vote) CheckTx(tx *types.Transaction, index int) error {
} }
if err != nil { if err != nil {
elog.Error("vote CheckTx", "txHash", txHash, "actionName", tx.ActionName(), "err", err, "actionData", action) elog.Error("vote CheckTx", "txHash", txHash, "actionName", tx.ActionName(), "err", err, "actionData", action.String())
} }
return err return err
} }
...@@ -152,13 +152,6 @@ func (v *vote) checkCommitVote(commit *vty.CommitVote, tx *types.Transaction, in ...@@ -152,13 +152,6 @@ func (v *vote) checkCommitVote(commit *vty.CommitVote, tx *types.Transaction, in
return err return err
} }
if voteInfo.BeginTimestamp > action.blockTime {
return errVoteNotStarted
}
if voteInfo.EndTimestamp <= action.blockTime {
return errVoteAlreadyFinished
}
if voteInfo.Status == voteStatusClosed { if voteInfo.Status == voteStatusClosed {
return errVoteAlreadyClosed return errVoteAlreadyClosed
} }
......
...@@ -146,7 +146,6 @@ func TestVote_CheckTx_CommitVote(t *testing.T) { ...@@ -146,7 +146,6 @@ func TestVote_CheckTx_CommitVote(t *testing.T) {
groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0)) groupID := formatGroupID(dapp.HeightIndexStr(testHeight, 0))
voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1)) voteID := formatVoteID(dapp.HeightIndexStr(testHeight, 1))
vote2 := formatVoteID(dapp.HeightIndexStr(testHeight, 7))
tcArr := []*testcase{{ tcArr := []*testcase{{
index: 0, index: 0,
payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}}, payload: &vty.CreateGroup{Name: "test", Members: []*vty.GroupMember{{Addr: testAddrs[0]}}},
...@@ -192,9 +191,10 @@ func TestVote_CheckTx_CommitVote(t *testing.T) { ...@@ -192,9 +191,10 @@ func TestVote_CheckTx_CommitVote(t *testing.T) {
}, },
execType: testTypeExecLocal, execType: testTypeExecLocal,
}, { }, {
index: 8, index: 8,
payload: &vty.CommitVote{VoteID: vote2}, payload: &vty.CommitVote{VoteID: formatVoteID(dapp.HeightIndexStr(testHeight, 7))},
expectCheckErr: errVoteNotStarted, execType: testTypeExec,
expectExecErr: errVoteNotStarted,
}} }}
testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0]) testExec(t, nil, testTypeCheckTx, tcArr, privKeys[0])
......
...@@ -44,6 +44,14 @@ func (v *vote) ExecLocal_UpdateGroup(update *vty.UpdateGroup, tx *types.Transact ...@@ -44,6 +44,14 @@ func (v *vote) ExecLocal_UpdateGroup(update *vty.UpdateGroup, tx *types.Transact
dbSet := &types.LocalDBSet{} dbSet := &types.LocalDBSet{}
groupInfo := decodeGroupInfo(receiptData.Logs[0].Log) groupInfo := decodeGroupInfo(receiptData.Logs[0].Log)
table := newGroupTable(v.GetLocalDB()) table := newGroupTable(v.GetLocalDB())
row, err := table.GetData([]byte(groupInfo.ID))
if err != nil {
elog.Error("execLocal updateGroup", "txHash", hex.EncodeToString(tx.Hash()), "groupTable get", err)
return nil, err
}
oldInfo, _ := row.Data.(*vty.GroupInfo)
// 状态数据中未保存投票个数信息,需要进行赋值
groupInfo.VoteNum = oldInfo.VoteNum
kvs, err := v.updateAndSaveTable(table.Replace, table.Save, groupInfo, tx, vty.NameUpdateGroupAction, "group") kvs, err := v.updateAndSaveTable(table.Replace, table.Save, groupInfo, tx, vty.NameUpdateGroupAction, "group")
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -84,21 +92,21 @@ func (v *vote) ExecLocal_UpdateGroup(update *vty.UpdateGroup, tx *types.Transact ...@@ -84,21 +92,21 @@ func (v *vote) ExecLocal_UpdateGroup(update *vty.UpdateGroup, tx *types.Transact
func (v *vote) ExecLocal_CreateVote(payload *vty.CreateVote, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (v *vote) ExecLocal_CreateVote(payload *vty.CreateVote, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{} dbSet := &types.LocalDBSet{}
voteInfo := decodeVoteInfo(receiptData.Logs[0].Log) voteInfo := decodeVoteInfo(receiptData.Logs[0].Log)
table := newVoteTable(v.GetLocalDB()) vTable := newVoteTable(v.GetLocalDB())
kvs, err := v.updateAndSaveTable(table.Add, table.Save, voteInfo, tx, vty.NameCreateVoteAction, "vote") gTable := newGroupTable(v.GetLocalDB())
row, err := gTable.GetData([]byte(voteInfo.GroupID))
if err != nil { if err != nil {
elog.Error("execLocal createVote", "txHash", hex.EncodeToString(tx.Hash()), "groupTable get", err)
return nil, err return nil, err
} }
dbSet.KV = kvs groupInfo, _ := row.Data.(*vty.GroupInfo)
table = newGroupTable(v.GetLocalDB()) groupInfo.VoteNum++
row, err := table.GetData([]byte(voteInfo.GroupID)) voteInfo.GroupName = groupInfo.GetName()
dbSet.KV, err = v.updateAndSaveTable(vTable.Add, vTable.Save, voteInfo, tx, vty.NameCreateVoteAction, "vote")
if err != nil { if err != nil {
elog.Error("execLocal createVote", "txHash", hex.EncodeToString(tx.Hash()), "voteTable get", err)
return nil, err return nil, err
} }
groupInfo, _ := row.Data.(*vty.GroupInfo) kvs, err := v.updateAndSaveTable(gTable.Replace, gTable.Save, groupInfo, tx, vty.NameCreateVoteAction, "group")
groupInfo.VoteNum++
kvs, err = v.updateAndSaveTable(table.Replace, table.Save, groupInfo, tx, vty.NameCreateVoteAction, "group")
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -97,10 +97,14 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) { ...@@ -97,10 +97,14 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) {
index: 0, index: 0,
payload: &vty.CreateGroup{Name: "test"}, payload: &vty.CreateGroup{Name: "test"},
}, { }, {
index: 1, index: 1,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: []string{testAddrs[0]}, AddAdmins: []string{testAddrs[1]}}, payload: &vty.CreateVote{Name: "v1", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}, { }, {
index: 2, index: 2,
payload: &vty.UpdateGroup{GroupID: groupID, RemoveAdmins: []string{testAddrs[0]}, AddAdmins: []string{testAddrs[1]}},
}, {
index: 3,
priv: privKeys[1], priv: privKeys[1],
payload: &vty.UpdateGroup{GroupID: groupID, RemoveMembers: testAddrs, AddMembers: members}, payload: &vty.UpdateGroup{GroupID: groupID, RemoveMembers: testAddrs, AddMembers: members},
}} }}
...@@ -122,7 +126,7 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) { ...@@ -122,7 +126,7 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) {
testTableData(t, table, tcArr1, "check member groupIDs") testTableData(t, table, tcArr1, "check member groupIDs")
table = newGroupTable(mock.exec.GetLocalDB()) table = newGroupTable(mock.exec.GetLocalDB())
expectInfo := &vty.GroupInfo{ID: groupID, Name: "test", Admins: []string{testAddrs[1]}, expectInfo := &vty.GroupInfo{ID: groupID, Name: "test", Admins: []string{testAddrs[1]},
Members: members, MemberNum: 1, Creator: testAddrs[0]} Members: members, MemberNum: 1, Creator: testAddrs[0], VoteNum: 1}
testTableData(t, table, []*tableCase{{ testTableData(t, table, []*tableCase{{
index: 0, index: 0,
key: []byte(groupID), key: []byte(groupID),
...@@ -136,7 +140,8 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) { ...@@ -136,7 +140,8 @@ func TestVote_ExecLocal_UpdateGroup(t *testing.T) {
tx := util.CreateNoneTx(mock.cfg, privKeys[0]) tx := util.CreateNoneTx(mock.cfg, privKeys[0])
group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID) group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, group.String(), expectInfo.String()) group.VoteNum = 1
require.Equal(t, expectInfo.String(), group.String())
} }
func TestVote_ExecLocal_CreateVote(t *testing.T) { func TestVote_ExecLocal_CreateVote(t *testing.T) {
...@@ -149,18 +154,26 @@ func TestVote_ExecLocal_CreateVote(t *testing.T) { ...@@ -149,18 +154,26 @@ func TestVote_ExecLocal_CreateVote(t *testing.T) {
options := []*vty.VoteOption{{Option: "A"}, {Option: "B"}} options := []*vty.VoteOption{{Option: "A"}, {Option: "B"}}
tcArr := []*testcase{{ tcArr := []*testcase{{
index: 0, index: 0,
payload: &vty.CreateGroup{Name: "test"}, payload: &vty.CreateGroup{Name: "g1"},
}, { }, {
index: 1, index: 1,
payload: &vty.CreateVote{Name: "test", GroupID: groupID, VoteOptions: []string{"A", "B"}, payload: &vty.CreateVote{Name: "v1", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}, {
index: 2,
payload: &vty.CreateVote{Name: "v2", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}, {
index: 3,
payload: &vty.CreateVote{Name: "v3", GroupID: groupID, VoteOptions: []string{"A", "B"},
BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1}, BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1},
}} }}
testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0]) testExec(t, mock, testTypeExecLocal, tcArr, privKeys[0])
table := newVoteTable(mock.exec.GetLocalDB()) table := newVoteTable(mock.exec.GetLocalDB())
expectVoteInfo := &vty.VoteInfo{ expectVoteInfo := &vty.VoteInfo{
Name: "test", VoteOptions: options, BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1, Name: "v1", VoteOptions: options, BeginTimestamp: testBlockTime, EndTimestamp: testBlockTime + 1,
GroupID: groupID, ID: voteID, Creator: testAddrs[0], GroupID: groupID, ID: voteID, Creator: testAddrs[0], GroupName: "g1",
} }
testTableData(t, table, []*tableCase{{ testTableData(t, table, []*tableCase{{
index: 0, index: 0,
...@@ -172,11 +185,10 @@ func TestVote_ExecLocal_CreateVote(t *testing.T) { ...@@ -172,11 +185,10 @@ func TestVote_ExecLocal_CreateVote(t *testing.T) {
row, err := table.GetData([]byte(groupID)) row, err := table.GetData([]byte(groupID))
require.Nil(t, err) require.Nil(t, err)
info, _ := row.Data.(*vty.GroupInfo) info, _ := row.Data.(*vty.GroupInfo)
require.Equal(t, uint32(1), info.VoteNum)
tx := util.CreateNoneTx(mock.cfg, privKeys[0]) tx := util.CreateNoneTx(mock.cfg, privKeys[0])
group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID) group, err := newAction(mock.exec, tx, 0).getGroupInfo(groupID)
require.Nil(t, err) require.Nil(t, err)
group.VoteNum = info.VoteNum group.VoteNum = 3
require.Equal(t, group.String(), info.String()) require.Equal(t, group.String(), info.String())
} }
...@@ -208,6 +220,7 @@ func TestVote_ExecLocal_CloseVote(t *testing.T) { ...@@ -208,6 +220,7 @@ func TestVote_ExecLocal_CloseVote(t *testing.T) {
tx := util.CreateNoneTx(mock.cfg, privKeys[0]) tx := util.CreateNoneTx(mock.cfg, privKeys[0])
vote, err := newAction(mock.exec, tx, 0).getVoteInfo(voteID) vote, err := newAction(mock.exec, tx, 0).getVoteInfo(voteID)
require.Nil(t, err) require.Nil(t, err)
vote.GroupName = "test"
require.Equal(t, vote.String(), info.String()) require.Equal(t, vote.String(), info.String())
} }
...@@ -243,6 +256,7 @@ func TestVote_ExecLocal_CommitVote(t *testing.T) { ...@@ -243,6 +256,7 @@ func TestVote_ExecLocal_CommitVote(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
vote.CommitInfos[0].TxHash = info.CommitInfos[0].TxHash vote.CommitInfos[0].TxHash = info.CommitInfos[0].TxHash
vote.CommitInfos[0].VoteWeight = info.CommitInfos[0].VoteWeight vote.CommitInfos[0].VoteWeight = info.CommitInfos[0].VoteWeight
vote.GroupName = "test"
require.Equal(t, vote.String(), info.String()) require.Equal(t, vote.String(), info.String())
} }
......
...@@ -104,6 +104,7 @@ message VoteInfo { ...@@ -104,6 +104,7 @@ message VoteInfo {
repeated CommitInfo commitInfos = 8; //已投票的提交信息 repeated CommitInfo commitInfos = 8; //已投票的提交信息
string description = 9; //描述信息 string description = 9; //描述信息
uint32 status = 10; //状态,1即将开始,2正在进行,3已经结束,4已关闭 uint32 status = 10; //状态,1即将开始,2正在进行,3已经结束,4已关闭
string groupName = 11; //所属投票组名称
} }
message VoteInfos { message VoteInfos {
......
...@@ -34,7 +34,7 @@ message GroupMember { ...@@ -34,7 +34,7 @@ message GroupMember {
##### 交易回执 ##### 交易回执
```proto ```proto
// 投票组信息 // 投票组信息
message GroupInfo { message GroupInfo {
...@@ -79,7 +79,7 @@ message GroupMember { ...@@ -79,7 +79,7 @@ message GroupMember {
``` ```
##### 交易回执 ##### 交易回执
```proto ```proto
// 投票组信息 // 投票组信息
message GroupInfo { message GroupInfo {
...@@ -223,6 +223,7 @@ message VoteInfo { ...@@ -223,6 +223,7 @@ message VoteInfo {
repeated CommitInfo commitInfos = 8; //已投票的提交信息 repeated CommitInfo commitInfos = 8; //已投票的提交信息
string description = 9; //描述信息 string description = 9; //描述信息
uint32 status = 10; //状态,1即将开始,2正在进行,3已经结束,4已关闭 uint32 status = 10; //状态,1即将开始,2正在进行,3已经结束,4已关闭
string groupName = 11; //所属投票组名称
} }
``` ```
...@@ -339,7 +340,7 @@ message ReqStrings { ...@@ -339,7 +340,7 @@ message ReqStrings {
message MemberInfos { message MemberInfos {
repeated MemberInfo memberList = 1; //投票组成员信息列表 repeated MemberInfo memberList = 1; //投票组成员信息列表
} }
message MemberInfo { message MemberInfo {
string addr = 1; //地址 string addr = 1; //地址
string name = 2; //用户名称 string name = 2; //用户名称
...@@ -351,7 +352,7 @@ message MemberInfo { ...@@ -351,7 +352,7 @@ message MemberInfo {
- 通用查询json rpc接口,Chain33.Query - 通用查询json rpc接口,Chain33.Query
- funcName: GetMembers - funcName: GetMembers
- -
```bash ```bash
curl -kd '{"method":"Chain33.Query","params":[{"execer":"vote","funcName":"GetMembers","payload":{"items":["1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5"]}}],"id":0}' http://localhost:8801 curl -kd '{"method":"Chain33.Query","params":[{"execer":"vote","funcName":"GetMembers","payload":{"items":["1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5"]}}],"id":0}' http://localhost:8801
``` ```
......
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