Commit de555afc authored by madengji's avatar madengji Committed by 33cn

para consens add bully and bls sign

parent e4dacdad
package para
import (
"errors"
"sync"
"time"
"github.com/33cn/chain33/queue"
"github.com/33cn/chain33/types"
pt "github.com/33cn/plugin/plugin/dapp/paracross/types"
)
const (
ELECTION = iota
COORDINATOR
OK
CLOSE
)
// Bully is a `struct` representing a single node used by the `Bully Algorithm`.
//
// NOTE: More details about the `Bully algorithm` can be found here
// https://en.wikipedia.org/wiki/Bully_algorithm .
type Bully struct {
ID string
coordinator string
nodegroup map[string]bool
inNodeGroup bool
mu *sync.RWMutex
receiveChan chan *pt.ElectionMsg
electionChan chan *pt.ElectionMsg
paraClient *client
qClient queue.Client
wg *sync.WaitGroup
quit chan struct{}
}
func NewBully(para *client, ID string, wg *sync.WaitGroup) (*Bully, error) {
b := &Bully{
paraClient: para,
ID: ID,
nodegroup: make(map[string]bool),
electionChan: make(chan *pt.ElectionMsg, 1),
receiveChan: make(chan *pt.ElectionMsg),
wg: wg,
mu: &sync.RWMutex{},
quit: make(chan struct{}),
}
return b, nil
}
func (b *Bully) SetParaAPI(cli queue.Client) {
b.qClient = cli
}
func (b *Bully) UpdateValidNodes(nodes []string) {
b.mu.Lock()
defer b.mu.Unlock()
b.nodegroup = make(map[string]bool)
for _, n := range nodes {
plog.Info("bully node update", "node", n)
b.nodegroup[n] = true
}
//退出nodegroup
if b.inNodeGroup && !b.nodegroup[b.ID] {
_ = b.Send("", CLOSE, nil)
}
b.inNodeGroup = b.nodegroup[b.ID]
}
func (b *Bully) isValidNode(ID string) bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.nodegroup[ID]
}
func (b *Bully) Close() {
close(b.quit)
}
func (b *Bully) Receive(msg *pt.ElectionMsg) {
plog.Info("bully rcv", "type", msg.Type)
switch msg.Type {
case CLOSE:
if msg.PeerID == b.Coordinator() {
b.SetCoordinator(b.ID)
b.Elect()
}
case OK:
if msg.ToID != b.ID {
return
}
select {
case b.electionChan <- msg:
return
case <-time.After(200 * time.Millisecond):
return
}
default:
b.receiveChan <- msg
}
}
func (b *Bully) sendMsg(ty int64, data interface{}) error {
msg := b.qClient.NewMessage("p2p", ty, data)
err := b.qClient.Send(msg, true)
if err != nil {
return err
}
resp, err := b.qClient.Wait(msg)
if err != nil {
return err
}
if resp.GetData().(*types.Reply).IsOk {
return nil
}
return errors.New(string(resp.GetData().(*types.Reply).GetMsg()))
}
func (b *Bully) Send(toId string, msgTy int32, data []byte) error {
act := &pt.ParaP2PSubMsg{Ty: P2pSubElectMsg}
act.Value = &pt.ParaP2PSubMsg_Election{Election: &pt.ElectionMsg{ToID: toId, PeerID: b.ID, Type: msgTy, Data: data}}
plog.Info("bull sendmsg")
err := b.paraClient.SendPubP2PMsg(types.Encode(act))
//err := b.sendMsg(types.EventPubTopicMsg, &types.PublishTopicMsg{Topic: "consensus", Msg: types.Encode(act)})
plog.Info("bully ret")
return err
}
// SetCoordinator sets `ID` as the new `b.coordinator` if `ID` is greater than
// `b.coordinator` or equal to `b.ID`.
func (b *Bully) SetCoordinator(ID string) {
b.mu.Lock()
defer b.mu.Unlock()
if ID > b.coordinator || ID == b.ID {
b.coordinator = ID
}
}
// Coordinator returns `b.coordinator`.
//
// NOTE: This function is thread-safe.
func (b *Bully) Coordinator() string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.coordinator
}
func (b *Bully) IsSelfCoordinator() bool {
b.mu.RLock()
defer b.mu.RUnlock()
return b.ID == b.coordinator
}
// Elect handles the leader election mechanism of the `Bully algorithm`.
func (b *Bully) Elect() {
_ = b.Send("", ELECTION, nil)
select {
case <-b.electionChan:
return
case <-time.After(time.Second):
b.SetCoordinator(b.ID)
_ = b.Send("", COORDINATOR, nil)
return
}
}
func (b *Bully) Run() {
defer b.wg.Done()
var feedDog = false
var feedDogTiker <-chan time.Time
var watchDogTiker <-chan time.Time
plog.Info("bully init")
onceTimer := time.NewTimer(time.Minute)
out:
for {
select {
case msg := <-b.receiveChan:
switch msg.Type {
case ELECTION:
if msg.PeerID < b.ID {
_ = b.Send(msg.PeerID, OK, nil)
b.Elect()
}
case COORDINATOR:
if !b.isValidNode(msg.PeerID) {
continue
}
b.SetCoordinator(msg.PeerID)
if b.coordinator < b.ID {
b.Elect()
}
feedDog = true
}
case <-onceTimer.C:
feedDogTiker = time.NewTicker(20 * time.Second).C
watchDogTiker = time.NewTicker(time.Minute).C
case <-feedDogTiker:
plog.Info("bully feed dog tiker", "is", b.IsSelfCoordinator(), "valid", b.isValidNode(b.ID))
//leader需要定期喂狗
if b.IsSelfCoordinator() && b.isValidNode(b.ID) {
_ = b.Send("", COORDINATOR, nil)
}
case <-watchDogTiker:
//至少1分钟内要收到leader喂狗消息,否则认为leader挂了,重新选举
if !feedDog {
b.Elect()
plog.Info("bully watchdog triger")
}
feedDog = false
case <-b.quit:
break out
}
}
}
# para bully algorithm
#1. 广播
1. 新节点起来后会监听leader节点的喂狗消息, 超时未收到,则发起选举流程
1. 如果收到消息后,发现比自己小,则发起选举流程
1. 如果本节点不是最大节点,则会收到比自己大的节点的OK回复,并自己大的节点整个网络发起选举流程,最后收到leader的通知
1. 如果本节点是最大节点,则不会收到ok,则最后确定自己是leader
1. leader节点会定期广播宣示主权的喂狗消息,如果有节点超过一定时间未收到喂狗消息,则发起选举,比非leader节点定期发心跳消息要少
1. leader节点会查看自己是否在nodegroup里面,如果不在,则发送close消息,触发重新选举 
This diff is collapsed.
This diff is collapsed.
# para bls sign
#1. 订阅topic
以自身账户为topic订阅,向平行链内部节点间广播
#2. 协商leader
广播之后一段时间,是寻找leader的过程
#3. 发送共识交易
1. 共识交易发给leader,由leader聚合后上链,如果收集的签名交易不超过2/3节点,则不发送上链交易
1. 共识交易发送后,等待回复(广播形式),如果超时未回复,则重新发送
2. leader的轮换算法可以下一步考虑
This diff is collapsed.
......@@ -553,6 +553,7 @@ out:
}
//如果当前正在追赶,暂不处理
if client.commitMsgClient.authAccount != "" && client.isCaughtUp() && len(paraTxs.Items) > 0 {
//在追赶上之后,每次seq只请求一个,只检查第一个即可
client.commitMsgClient.commitTxCheckNotify(paraTxs.Items[0])
}
......
......@@ -68,6 +68,26 @@ type multiDldClient struct {
mtx sync.Mutex
}
func newMultiDldCli(para *client, cfg *subConfig) *multiDldClient {
multi := &multiDldClient{
paraClient: para,
invNumPerJob: defaultInvNumPerJob,
jobBufferNum: defaultJobBufferNum,
serverTimeout: maxServerRspTimeout,
}
if cfg.MultiDownInvNumPerJob > 0 {
multi.invNumPerJob = cfg.MultiDownInvNumPerJob
}
if cfg.MultiDownJobBuffNum > 0 {
multi.jobBufferNum = cfg.MultiDownJobBuffNum
}
if cfg.MultiDownServerRspTime > 0 {
multi.serverTimeout = cfg.MultiDownServerRspTime
}
return multi
}
func (m *multiDldClient) getInvs(startHeight, endHeight int64) []*inventory {
var invs []*inventory
if endHeight > startHeight && endHeight-startHeight > maxRollbackHeight {
......
// 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 para
import (
"fmt"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/types"
pt "github.com/33cn/plugin/plugin/dapp/paracross/types"
)
//IsCaughtUp 是否追上最新高度,
func (client *client) Query_IsCaughtUp(req *types.ReqNil) (types.Message, error) {
if client == nil {
return nil, fmt.Errorf("%s", "client not bind message queue.")
}
return &types.IsCaughtUp{Iscaughtup: client.isCaughtUp()}, nil
}
func (client *client) Query_LocalBlockInfo(req *types.ReqInt) (types.Message, error) {
if client == nil {
return nil, fmt.Errorf("%s", "client not bind message queue.")
}
var block *pt.ParaLocalDbBlock
var err error
if req.Height <= -1 {
block, err = client.getLastLocalBlock()
if err != nil {
return nil, err
}
} else {
block, err = client.getLocalBlockByHeight(req.Height)
if err != nil {
return nil, err
}
}
blockInfo := &pt.ParaLocalDbBlockInfo{
Height: block.Height,
MainHash: common.ToHex(block.MainHash),
MainHeight: block.MainHeight,
ParentMainHash: common.ToHex(block.ParentMainHash),
BlockTime: block.BlockTime,
}
for _, tx := range block.Txs {
blockInfo.Txs = append(blockInfo.Txs, common.ToHex(tx.Hash()))
}
return blockInfo, nil
}
func (client *client) Query_LeaderInfo(req *types.ReqNil) (types.Message, error) {
if client == nil {
return nil, fmt.Errorf("%s", "client not bind message queue.")
}
isLeader := client.bullyCli.IsSelfCoordinator()
leader := client.bullyCli.Coordinator()
return &pt.ElectionStatus{IsLeader: isLeader, LeaderId: leader}, nil
}
func (client *client) Query_CommitTxInfo(req *types.ReqNil) (types.Message, error) {
if client == nil {
return nil, fmt.Errorf("%s", "client not bind message queue.")
}
rt := client.blsSignCli.showTxBuffInfo()
return rt, nil
}
// Query_CreateNewAccount 通知para共识模块钱包创建了一个新的账户
func (client *client) Query_CreateNewAccount(acc *types.Account) (types.Message, error) {
if acc == nil {
return nil, types.ErrInvalidParam
}
plog.Info("Query_CreateNewAccount", "acc", acc.Addr)
// 需要para共识这边处理新创建的账户是否是超级节点发送commit共识交易的账户
client.commitMsgClient.onWalletAccount(acc)
return &types.Reply{IsOk: true, Msg: []byte("OK")}, nil
}
// Query_WalletStatus 通知para共识模块钱包锁状态有变化
func (client *client) Query_WalletStatus(walletStatus *types.WalletStatus) (types.Message, error) {
if walletStatus == nil {
return nil, types.ErrInvalidParam
}
plog.Info("Query_WalletStatus", "walletStatus", walletStatus.IsWalletLock)
// 需要para共识这边根据walletStatus.IsWalletLock锁的状态开启/关闭发送共识交易
client.commitMsgClient.onWalletStatus(walletStatus)
return &types.Reply{IsOk: true, Msg: []byte("OK")}, nil
}
......@@ -44,6 +44,8 @@ func ParcCmd() *cobra.Command {
GetBlockInfoCmd(),
GetLocalBlockInfoCmd(),
GetConsensDoneInfoCmd(),
LeaderCmd(),
CmtTxInfoCmd(),
)
return cmd
}
......@@ -950,6 +952,40 @@ func isSync(cmd *cobra.Command, args []string) {
ctx.Run()
}
// IsSyncCmd query parachain is sync
func LeaderCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "leader",
Short: "node leader info",
Run: leaderInfo,
}
return cmd
}
func leaderInfo(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
var res pt.ElectionStatus
ctx := jsonclient.NewRPCCtx(rpcLaddr, "paracross.GetParaNodeLeaderInfo", nil, &res)
ctx.Run()
}
// IsSyncCmd query parachain is sync
func CmtTxInfoCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "cmtinfo",
Short: "commit tx info",
Run: cmtTxInfo,
}
return cmd
}
func cmtTxInfo(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
var res pt.ParaBlsSignSumInfo
ctx := jsonclient.NewRPCCtx(rpcLaddr, "paracross.GetParaCmtTxInfo", nil, &res)
ctx.Run()
}
func consusHeight(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
paraName, _ := cmd.Flags().GetString("paraName")
......
......@@ -133,8 +133,8 @@ func checkCommitInfo(cfg *types.Chain33Config, commit *pt.ParacrossNodeStatus) e
}
//区块链中不能使用float类型
func isCommitDone(nodes map[string]struct{}, mostSame int) bool {
return 3*mostSame > 2*len(nodes)
func isCommitDone(nodes, mostSame int) bool {
return 3*mostSame > 2*nodes
}
func makeCommitReceipt(addr string, commit *pt.ParacrossNodeStatus, prev, current *pt.ParacrossHeightStatus) *types.Receipt {
......@@ -226,14 +226,14 @@ func makeDoneReceipt(cfg *types.Chain33Config, execMainHeight, execHeight int64,
}
}
func getMostCommit(stat *pt.ParacrossHeightStatus) (int, string) {
func GetMostCommit(commits [][]byte) (int, string) {
stats := make(map[string]int)
n := len(stat.Details.Addrs)
n := len(commits)
for i := 0; i < n; i++ {
if _, ok := stats[string(stat.Details.BlockHash[i])]; ok {
stats[string(stat.Details.BlockHash[i])]++
if _, ok := stats[string(commits[i])]; ok {
stats[string(commits[i])]++
} else {
stats[string(stat.Details.BlockHash[i])] = 1
stats[string(commits[i])] = 1
}
}
most := -1
......@@ -343,7 +343,7 @@ func paraCheckSelfConsOn(cfg *types.Chain33Config, db dbm.KV, commit *pt.Paracro
return true, nil, nil
}
func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus) error {
func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus, commitAddr string) error {
cfg := a.api.GetConfig()
err := checkCommitInfo(cfg, commit)
if err != nil {
......@@ -354,18 +354,19 @@ func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus) error {
if err != nil {
return err
}
if !validNode(a.fromaddr, nodesMap) {
return errors.Wrapf(pt.ErrNodeNotForTheTitle, "not validNode:%s", a.fromaddr)
//允许a.fromAddr非nodegroup成员,将来也许可以代nodegroup提交共识交易
if !validNode(commitAddr, nodesMap) {
return errors.Wrapf(pt.ErrNodeNotForTheTitle, "not validNode:%s", commitAddr)
}
titleStatus, err := getTitle(a.db, calcTitleKey(commit.Title))
if err != nil {
return errors.Wrapf(err, "getTitle:%s", a.fromaddr)
return errors.Wrapf(err, "getTitle:%s", commitAddr)
}
if titleStatus.Height+1 == commit.Height && commit.Height > 0 && !pt.IsParaForkHeight(cfg, commit.MainBlockHeight, pt.ForkLoopCheckCommitTxDone) {
if !bytes.Equal(titleStatus.BlockHash, commit.PreBlockHash) {
clog.Error("paracross.Commit", "check PreBlockHash", common.ToHex(titleStatus.BlockHash),
"commit tx", common.ToHex(commit.PreBlockHash), "commitheit", commit.Height, "from", a.fromaddr)
"commit tx", common.ToHex(commit.PreBlockHash), "commitheit", commit.Height, "from", commitAddr)
return pt.ErrParaBlockHashNoMatch
}
}
......@@ -378,7 +379,7 @@ func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus) error {
if !cfg.IsPara() {
blockHash, err := getBlockHash(a.api, commit.MainBlockHeight)
if err != nil {
clog.Error("paracross.Commit getBlockHash", "err", err, "commit tx height", commit.MainBlockHeight, "from", a.fromaddr)
clog.Error("paracross.Commit getBlockHash", "err", err, "commit tx height", commit.MainBlockHeight, "from", commitAddr)
return err
}
dbMainHash = blockHash.Hash
......@@ -386,7 +387,7 @@ func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus) error {
} else {
block, err := getBlockInfo(a.api, commit.Height)
if err != nil {
clog.Error("paracross.Commit getBlockInfo", "err", err, "height", commit.Height, "from", a.fromaddr)
clog.Error("paracross.Commit getBlockInfo", "err", err, "height", commit.Height, "from", commitAddr)
return err
}
dbMainHash = block.MainHash
......@@ -397,7 +398,7 @@ func (a *action) preCheckCommitInfo(commit *pt.ParacrossNodeStatus) error {
if !bytes.Equal(dbMainHash, commit.MainBlockHash) && commit.Height > 0 {
clog.Error("paracross.Commit blockHash not match", "isMain", !cfg.IsPara(), "db", common.ToHex(dbMainHash),
"commit", common.ToHex(commit.MainBlockHash), "commitHeight", commit.Height,
"commitMainHeight", commit.MainBlockHeight, "from", a.fromaddr)
"commitMainHeight", commit.MainBlockHeight, "from", commitAddr)
return types.ErrBlockHashNoMatch
}
......@@ -414,12 +415,6 @@ func (a *action) Commit(commit *pt.ParacrossCommitAction) (*types.Receipt, error
return receipt, err
}
}
err := a.preCheckCommitInfo(commit.Status)
if err != nil {
return nil, err
}
if commit.Bls != nil {
return a.commitBls(commit)
}
......@@ -450,6 +445,11 @@ func (a *action) commitBls(commit *pt.ParacrossCommitAction) (*types.Receipt, er
func (a *action) proCommitMsg(commit *pt.ParacrossNodeStatus, commitAddr string) (*types.Receipt, error) {
cfg := a.api.GetConfig()
err := a.preCheckCommitInfo(commit, commitAddr)
if err != nil {
return nil, err
}
nodes, _, err := a.getNodesGroup(commit.Title)
if err != nil {
return nil, err
......@@ -553,7 +553,7 @@ func (a *action) verifyBlsSign(commit *pt.ParacrossCommitAction) ([]string, erro
}
//1. 获取addr对应的bls 公钥
signAddrs := getAddrsByBitMap(nodesArry, commit.Bls.Addrs)
signAddrs := getAddrsByBitMap(nodesArry, commit.Bls.AddrsMap)
var pubs []string
for _, addr := range signAddrs {
pub, err := getAddrBlsPubKey(a.db, commit.Status.Title, addr)
......@@ -597,7 +597,7 @@ func (a *action) verifyBlsSign(commit *pt.ParacrossCommitAction) ([]string, erro
if !g2pubs.Verify(msg, aPub, sign) {
clog.Error("paracross.Commit bls sign verify", "title", commit.Status.Title, "height", commit.Status.Height,
"addrsMap", common.ToHex(commit.Bls.Addrs), "sign", common.ToHex(commit.Bls.Sign), "addr", signAddrs, "nodes", nodesArry)
"addrsMap", common.ToHex(commit.Bls.AddrsMap), "sign", common.ToHex(commit.Bls.Sign), "addr", signAddrs, "nodes", nodesArry)
clog.Error("paracross.commit bls sign verify", "data", common.ToHex(msg), "height", commit.Status.Height)
return nil, pt.ErrBlsSignVerify
}
......@@ -615,8 +615,8 @@ func (a *action) commitTxDone(nodeStatus *pt.ParacrossNodeStatus, stat *pt.Parac
}
commitCount := len(stat.Details.Addrs)
most, mostHash := getMostCommit(stat)
if !isCommitDone(nodes, most) {
most, mostHash := GetMostCommit(stat.Details.BlockHash)
if !isCommitDone(len(nodes), most) {
return receipt, nil
}
clog.Debug("paracross.Commit commit ----pass", "most", most, "mostHash", common.ToHex([]byte(mostHash)))
......@@ -794,8 +794,8 @@ func (a *action) commitTxDoneByStat(stat *pt.ParacrossHeightStatus, titleStatus
updateCommitAddrs(stat, nodes)
commitCount := len(stat.Details.Addrs)
most, mostHash := getMostCommit(stat)
if !isCommitDone(nodes, most) {
most, mostHash := GetMostCommit(stat.Details.BlockHash)
if !isCommitDone(len(nodes), most) {
return nil, nil
}
clog.Debug("paracross.commitTxDoneByStat ----pass", "most", most, "mostHash", common.ToHex([]byte(mostHash)))
......
......@@ -274,7 +274,7 @@ func (a *action) stageVote(config *pt.ConfigVoteInfo) (*types.Receipt, error) {
stat.Votes = updateVotes(stat.Votes, nodes)
most, vote := getMostVote(stat.Votes)
if !isCommitDone(nodes, most) {
if !isCommitDone(len(nodes), most) {
return makeStageConfigReceipt(copyStat, stat), nil
}
clog.Info("paracross.stageVote ----pass", "most", most, "pass", vote)
......
......@@ -587,7 +587,7 @@ func (a *action) nodeVote(config *pt.ParaNodeAddrConfig) (*types.Receipt, error)
stat.Votes = updateVotes(stat.Votes, nodes)
most, vote := getMostVote(stat.Votes)
if !isCommitDone(nodes, most) {
if !isCommitDone(len(nodes), most) {
superManagerPass, err := a.checkIsSuperManagerVote(config, nodes)
if err != nil {
return nil, err
......
......@@ -266,8 +266,9 @@ message ReplyQuerySelfStages {
}
message ParacrossCommitBlsInfo {
bytes sign = 1;
bytes addrs = 2; //addrs' bitmap
bytes sign = 1;
bytes addrsMap = 2; //addrs' bitmap
repeated string addrs = 3; //addr's array
}
message ParacrossCommitAction {
......@@ -414,6 +415,39 @@ message ParaLocalDbBlockInfo {
repeated string txs = 6;
}
message ParaBlsSignSumDetails {
repeated string addrs = 1;
repeated bytes msgs = 2;
repeated bytes signs = 3;
int64 height = 4;
}
message ParaBlsSignSumInfo {
repeated ParaBlsSignSumDetails info = 1;
}
message ElectionMsg {
string toID = 1; //target id
string peerID = 2; //src id
int32 type = 3;
bytes data = 4;
}
message ParaP2PSubMsg {
int32 ty = 1;
oneof value {
Transaction commitTx = 10;
ElectionMsg election = 11;
}
}
message ElectionStatus {
bool isLeader = 1;
string leaderId = 2;
}
service paracross {
rpc IsSync(ReqNil) returns (IsCaughtUp) {}
}
\ No newline at end of file
......@@ -52,3 +52,41 @@ func (c *Jrpc) GetParaLocalBlockInfo(in *types.ReqInt, result *interface{}) erro
*result = data
return nil
}
// GetParaLocalBlockInfo query para chain the download layer's local height
func (c *channelClient) GetParaNodeLeaderInfo(ctx context.Context, in *types.ReqNil) (*pt.ElectionStatus, error) {
data, err := c.QueryConsensusFunc("para", "LeaderInfo", in)
if err != nil {
return nil, err
}
return data.(*pt.ElectionStatus), nil
}
// GetParaLocalBlockInfo query para local height
func (c *Jrpc) GetParaNodeLeaderInfo(in *types.ReqNil, result *interface{}) error {
data, err := c.cli.GetParaNodeLeaderInfo(context.Background(), in)
if err != nil {
return err
}
*result = data
return nil
}
// GetParaLocalBlockInfo query para chain the download layer's local height
func (c *channelClient) GetParaCmtTxInfo(ctx context.Context, in *types.ReqNil) (*pt.ParaBlsSignSumInfo, error) {
data, err := c.QueryConsensusFunc("para", "CommitTxInfo", in)
if err != nil {
return nil, err
}
return data.(*pt.ParaBlsSignSumInfo), nil
}
// GetParaLocalBlockInfo query para local height
func (c *Jrpc) GetParaCmtTxInfo(in *types.ReqNil, result *interface{}) error {
data, err := c.cli.GetParaCmtTxInfo(context.Background(), in)
if err != nil {
return err
}
*result = data
return nil
}
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