待解决问题

1、最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

class Solution {
public String longestPalindrome(String s) {

}
}

2、分数排名

编写一个 SQL 查询来实现分数排名。

如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。

+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):

+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
重要提示:对于 MySQL 解决方案,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号。例如 `Rank`

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rank-scores
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

两种解法,排序后添加序号,或自连接

解法1,自连接


SELECT
s1.Score,
COUNT(DISTINCT s2.Score) + 1 Rank
FROM
Scores s1
LEFT JOIN Scores s2 ON s2.Score > s1.Score
GROUP BY
s1.id,
s1.Score
ORDER BY
s1.Score DESC
解法2,排序后添加序号


SELECT
t.Score,
CAST(t.Rank AS SIGNED) Rank
FROM
(
SELECT
s.Score,
IF(s.Score = @pre_s, @rank := @rank, @rank := @rank + 1) Rank,
@pre_s := s.Score pre_s
FROM
Scores s,
(SELECT @pre_s := NULL, @rank := 0) t
ORDER BY
Score DESC
) t

-------------------------------------------------

dpos

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "log"
    "math/big"
    "sort"
    "strconv"
    "time"
)

const (
    voteNodeNum      = 100
    superNodeNum     = 10
    mineSuperNodeNum = 3
)

type block struct {
    //上一个块的hash
    prehash string
    //本块hash
    hash string
    //时间戳
    timestamp string
    //区块内容
    data string
    //区块高度
    height int
    //挖出本块的节点地址
    address string
}

//用于存储区块链
var blockchain []block

//普通节点
type node struct {
    //代币数量
    votes int
    //节点地址
    address string
}

//竞选节点
type superNode struct {
    node
}

//投票节点池
var voteNodesPool []node

//竞选节点池
var starNodesPool []superNode

//存放可以挖矿的超级节点池
var superStarNodesPool []superNode

//生成新的区块
func generateNewBlock(oldBlock block, data string, address string) block {
    newBlock := block{}
    newBlock.prehash = oldBlock.hash
    newBlock.data = data
    newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05")
    newBlock.height = oldBlock.height + 1
    newBlock.address = address
    newBlock.getHash()
    return newBlock
}

//对自身进行散列
func (b *block) getHash() {
    sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height)
    hash := sha256.Sum256([]byte(sumString))
    b.hash = hex.EncodeToString(hash[:])
}

//投票
func voting() {
    for _, v := range voteNodesPool {
        rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1))
        if err != nil {
            log.Panic(err)
        }
        starNodesPool[int(rInt.Int64())].votes += v.votes
    }
}

//对挖矿节点进行排序
func sortMineNodes() {
    sort.Slice(starNodesPool, func(i, j int) bool {
        return starNodesPool[i].votes > starNodesPool[j].votes
    })
    superStarNodesPool = starNodesPool[:mineSuperNodeNum]
}

//初始化
func init() {
    //初始化投票节点
    for i := 0; i <= voteNodeNum; i++ {
        rInt, err := rand.Int(rand.Reader, big.NewInt(10000))
        if err != nil {
            log.Panic(err)
        }
        voteNodesPool = append(voteNodesPool, node{int(rInt.Int64()), "投票节点" + strconv.Itoa(i)})
    }
    //初始化竞选节点
    for i := 0; i <= superNodeNum; i++ {
        starNodesPool = append(starNodesPool, superNode{node{0, "超级节点" + strconv.Itoa(i)}})
    }
}

func main() {
    fmt.Println("初始化", voteNodeNum, "个投票节点...")
    fmt.Println(voteNodesPool)
    fmt.Println("当前存在的", superNodeNum, "个竞选节点")
    fmt.Println(starNodesPool)
    fmt.Println("投票节点们开始进行投票...")
    voting()
    fmt.Println("结束投票,查看竞选节点们获得票数...")
    fmt.Println(starNodesPool)
    fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点")
    sortMineNodes()
    fmt.Println(superStarNodesPool)
    fmt.Println("开始挖矿...")
    genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"}
    genesisBlock.getHash()
    blockchain = append(blockchain, genesisBlock)
    fmt.Println(blockchain[0])
    i, j := 0, 0
    for {
        time.Sleep(time.Second)
        newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address)
        blockchain = append(blockchain, newBlock)
        fmt.Println(blockchain[i+1])
        i++
        j++
        j = j % len(superStarNodesPool) //超级节点轮循获得出块权
    }
}
>股份授权证明机制是POS的一个变种,简单来说就是你手里有选票(币就相当于选票)。这时一些正在竞选超级节点的大节点们说把票都投给我把,等我当选了超级节点,我吃肉你喝汤,岂不美哉?然后你就信了,把票投给了竞选节点,这些节点竞选成功成为超级节点后会轮循的获得出块权。旷工费、通胀放出的代币也就都到了他们手里了。比较中心化的一种共识机制,但是TPS很高。

<br>

区块结构:
```go
type block struct {
    //上一个块的hash
    prehash string
    //本块hash
    hash string
    //时间戳
    timestamp string
    //区块内容
    data string
    //区块高度
    height int
    //挖出本块的节点地址
    address string
}
```

```go
//用于存储区块链
var blockchain []block
//普通节点
type node struct{
    //代币数量
    votes int
    //节点地址
    address string
}
//竞选节点
type superNode struct {
     node
}
//投票节点池
var voteNodesPool []node
//竞选节点池
var starNodesPool []superNode
//存放可以挖矿的超级节点池
var superStarNodesPool []superNode
```

初始化:\
生成投票节点池并随机赋予代币数量,同时为竞选节点池生成竞选节点
```go
//初始化
func init() {
    //初始化投票节点
    for i:=0;i<=voteNodeNum;i++ {
        rInt,err:=rand.Int(rand.Reader,big.NewInt(10000))
        if err != nil {
            log.Panic(err)
        }
        voteNodesPool = append(voteNodesPool,node{int(rInt.Int64()),"投票节点"+strconv.Itoa(i)})
    }
    //初始化超级节点
    for i:=0;i<=superNodeNum;i++ {
        starNodesPool = append(starNodesPool,superNode{node{0,"超级节点"+strconv.Itoa(i)}})
    }
}
```


模拟普通节点投票(随机的对竞选节点投票)
```go
//投票
func voting() {
    for _, v := range voteNodesPool {
        rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1))
        if err != nil {
            log.Panic(err)
        }
        starNodesPool[int(rInt.Int64())].votes += v.votes
    }
}
```

对竞选节点根据得票数排序,前几名成为超级节点
```go
//对挖矿节点进行排序
func sortMineNodes() {
    sort.Slice(starNodesPool, func(i, j int) bool {
        return starNodesPool[i].votes > starNodesPool[j].votes
    })
    superStarNodesPool = starNodesPool[:mineSuperNodeNum]
}
```

主函数
```go
func main() {
    fmt.Println("初始化", voteNodeNum, "个投票节点...")
    fmt.Println(voteNodesPool)
    fmt.Println("当前存在的", superNodeNum, "个竞选节点")
    fmt.Println(starNodesPool)
    fmt.Println("投票节点们开始进行投票...")
    voting()
    fmt.Println("结束投票,查看竞选节点们获得票数...")
    fmt.Println(starNodesPool)
    fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点")
    sortMineNodes()
    fmt.Println(superStarNodesPool)
    fmt.Println("开始挖矿...")
    genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"}
    genesisBlock.getHash()
    blockchain = append(blockchain, genesisBlock)
    fmt.Println(blockchain[0])
    i, j := 0, 0
    for {
        time.Sleep(time.Second)
        newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address)
        blockchain = append(blockchain, newBlock)
        fmt.Println(blockchain[i+1])
        i++
        j++
        j = j % len(superStarNodesPool)
    }
}
```

运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211162452121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)

pbft

2.1、client.go

package main

import (
    "bufio"
    "crypto/rand"
    "encoding/json"
    "fmt"
    "log"
    "math/big"
    "os"
    "strings"
    "time"
)

func clientSendMessageAndListen() {
    //开启客户端的本地监听(主要用来接收节点的reply信息)
    go clientTcpListen()
    fmt.Printf("客户端开启监听,地址:%s\n", clientAddr)

    fmt.Println(" ---------------------------------------------------------------------------------")
    fmt.Println("|  已进入PBFT测试Demo客户端,请启动全部节点后再发送消息! :)  |")
    fmt.Println(" ---------------------------------------------------------------------------------")
    fmt.Println("请在下方输入要存入节点的信息:")
    //首先通过命令行获取用户输入
    stdReader := bufio.NewReader(os.Stdin)
    for {
        data, err := stdReader.ReadString('\n')
        if err != nil {
            fmt.Println("Error reading from stdin")
            panic(err)
        }
        r := new(Request)
        r.Timestamp = time.Now().UnixNano()
        r.ClientAddr = clientAddr
        r.Message.ID = getRandom()
        //消息内容就是用户的输入
        r.Message.Content = strings.TrimSpace(data)
        br, err := json.Marshal(r)
        if err != nil {
            log.Panic(err)
        }
        fmt.Println(string(br))
        content := jointMessage(cRequest, br)
        //默认N0为主节点,直接把请求信息发送至N0
        tcpDial(content, nodeTable["N0"])
    }
}

//返回一个十位数的随机数,作为msgid
func getRandom() int {
    x := big.NewInt(10000000000)
    for {
        result, err := rand.Int(rand.Reader, x)
        if err != nil {
            log.Panic(err)
        }
        if result.Int64() > 1000000000 {
            return int(result.Int64())
        }
    }
}

2.2、cmd.go

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "log"
)

//<REQUEST,o,t,c>
type Request struct {
    Message
    Timestamp int64
    //相当于clientID
    ClientAddr string
}

//<<PRE-PREPARE,v,n,d>,m>
type PrePrepare struct {
    RequestMessage Request
    Digest         string
    SequenceID     int
    Sign           []byte
}

//<PREPARE,v,n,d,i>
type Prepare struct {
    Digest     string
    SequenceID int
    NodeID     string
    Sign       []byte
}

//<COMMIT,v,n,D(m),i>
type Commit struct {
    Digest     string
    SequenceID int
    NodeID     string
    Sign       []byte
}

//<REPLY,v,t,c,i,r>
type Reply struct {
    MessageID int
    NodeID    string
    Result    bool
}

type Message struct {
    Content string
    ID      int
}

const prefixCMDLength = 12

type command string

const (
    cRequest    command = "request"
    cPrePrepare command = "preprepare"
    cPrepare    command = "prepare"
    cCommit     command = "commit"
)

//默认前十二位为命令名称
func jointMessage(cmd command, content []byte) []byte {
    b := make([]byte, prefixCMDLength)
    for i, v := range []byte(cmd) {
        b[i] = v
    }
    joint := make([]byte, 0)
    joint = append(b, content...)
    return joint
}

//默认前十二位为命令名称
func splitMessage(message []byte) (cmd string, content []byte) {
    cmdBytes := message[:prefixCMDLength]
    newCMDBytes := make([]byte, 0)
    for _, v := range cmdBytes {
        if v != byte(0) {
            newCMDBytes = append(newCMDBytes, v)
        }
    }
    cmd = string(newCMDBytes)
    content = message[prefixCMDLength:]
    return
}

//对消息详情进行摘要
func getDigest(request Request) string {
    b, err := json.Marshal(request)
    if err != nil {
        log.Panic(err)
    }
    hash := sha256.Sum256(b)
    //进行十六进制字符串编码
    return hex.EncodeToString(hash[:])
}

2.3、main.go

package main

import (
    "log"
    "os"
)

const nodeCount = 4

//客户端的监听地址
var clientAddr = "127.0.0.1:8888"

//节点池,主要用来存储监听地址
var nodeTable map[string]string

func main() {
    //为四个节点生成公私钥
    genRsaKeys()
    nodeTable = map[string]string{
        "N0": "127.0.0.1:8000",
        "N1": "127.0.0.1:8001",
        "N2": "127.0.0.1:8002",
        "N3": "127.0.0.1:8003",
    }
    if len(os.Args) != 2 {
        log.Panic("输入的参数有误!")
    }
    nodeID := os.Args[1]
    if nodeID == "client" {
        clientSendMessageAndListen() //启动客户端程序
    } else if addr, ok := nodeTable[nodeID]; ok {
        p := NewPBFT(nodeID, addr)
        go p.tcpListen() //启动节点
    } else {
        log.Fatal("无此节点编号!")
    }
    select {}
}

2.4、pbft.go

package main

import (
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "strconv"
    "sync"
)

//本地消息池(模拟持久化层),只有确认提交成功后才会存入此池
var localMessagePool = []Message{}

type node struct {
    //节点ID
    nodeID string
    //节点监听地址
    addr string
    //RSA私钥
    rsaPrivKey []byte
    //RSA公钥
    rsaPubKey []byte
}

type pbft struct {
    //节点信息
    node node
    //每笔请求自增序号
    sequenceID int
    //
    lock sync.Mutex
    //临时消息池,消息摘要对应消息本体
    messagePool map[string]Request
    //存放收到的prepare数量(至少需要收到并确认2f个),根据摘要来对应
    prePareConfirmCount map[string]map[string]bool
    //存放收到的commit数量(至少需要收到并确认2f+1个),根据摘要来对应
    commitConfirmCount map[string]map[string]bool
    //该笔消息是否已进行Commit广播
    isCommitBordcast map[string]bool
    //该笔消息是否已对客户端进行Reply
    isReply map[string]bool
}

func NewPBFT(nodeID, addr string) *pbft {
    p := new(pbft)
    p.node.nodeID = nodeID
    p.node.addr = addr
    p.node.rsaPrivKey = p.getPivKey(nodeID) //从生成的私钥文件处读取
    p.node.rsaPubKey = p.getPubKey(nodeID)  //从生成的私钥文件处读取
    p.sequenceID = 0
    p.messagePool = make(map[string]Request)
    p.prePareConfirmCount = make(map[string]map[string]bool)
    p.commitConfirmCount = make(map[string]map[string]bool)
    p.isCommitBordcast = make(map[string]bool)
    p.isReply = make(map[string]bool)
    return p
}

func (p *pbft) handleRequest(data []byte) {
    //切割消息,根据消息命令调用不同的功能
    cmd, content := splitMessage(data)
    switch command(cmd) {
    case cRequest:
        p.handleClientRequest(content)
    case cPrePrepare:
        p.handlePrePrepare(content)
    case cPrepare:
        p.handlePrepare(content)
    case cCommit:
        p.handleCommit(content)
    }
}

//处理客户端发来的请求
func (p *pbft) handleClientRequest(content []byte) {
    fmt.Println("主节点已接收到客户端发来的request ...")
    //使用json解析出Request结构体
    r := new(Request)
    err := json.Unmarshal(content, r)
    if err != nil {
        log.Panic(err)
    }
    //添加信息序号
    p.sequenceIDAdd()
    //获取消息摘要
    digest := getDigest(*r)
    fmt.Println("已将request存入临时消息池")
    //存入临时消息池
    p.messagePool[digest] = *r
    //主节点对消息摘要进行签名
    digestByte, _ := hex.DecodeString(digest)
    signInfo := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey)
    //拼接成PrePrepare,准备发往follower节点
    pp := PrePrepare{*r, digest, p.sequenceID, signInfo}
    b, err := json.Marshal(pp)
    if err != nil {
        log.Panic(err)
    }
    fmt.Println("正在向其他节点进行进行PrePrepare广播 ...")
    //进行PrePrepare广播
    p.broadcast(cPrePrepare, b)
    fmt.Println("PrePrepare广播完成")
}

//处理预准备消息
func (p *pbft) handlePrePrepare(content []byte) {
    fmt.Println("本节点已接收到主节点发来的PrePrepare ...")
    //    //使用json解析出PrePrepare结构体
    pp := new(PrePrepare)
    err := json.Unmarshal(content, pp)
    if err != nil {
        log.Panic(err)
    }
    //获取主节点的公钥,用于数字签名验证
    primaryNodePubKey := p.getPubKey("N0")
    digestByte, _ := hex.DecodeString(pp.Digest)
    if digest := getDigest(pp.RequestMessage); digest != pp.Digest {
        fmt.Println("信息摘要对不上,拒绝进行prepare广播")
    } else if p.sequenceID+1 != pp.SequenceID {
        fmt.Println("消息序号对不上,拒绝进行prepare广播")
    } else if !p.RsaVerySignWithSha256(digestByte, pp.Sign, primaryNodePubKey) {
        fmt.Println("主节点签名验证失败!,拒绝进行prepare广播")
    } else {
        //序号赋值
        p.sequenceID = pp.SequenceID
        //将信息存入临时消息池
        fmt.Println("已将消息存入临时节点池")
        p.messagePool[pp.Digest] = pp.RequestMessage
        //节点使用私钥对其签名
        sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey)
        //拼接成Prepare
        pre := Prepare{pp.Digest, pp.SequenceID, p.node.nodeID, sign}
        bPre, err := json.Marshal(pre)
        if err != nil {
            log.Panic(err)
        }
        //进行准备阶段的广播
        fmt.Println("正在进行Prepare广播 ...")
        p.broadcast(cPrepare, bPre)
        fmt.Println("Prepare广播完成")
    }
}

//处理准备消息
func (p *pbft) handlePrepare(content []byte) {
    //使用json解析出Prepare结构体
    pre := new(Prepare)
    err := json.Unmarshal(content, pre)
    if err != nil {
        log.Panic(err)
    }
    fmt.Printf("本节点已接收到%s节点发来的Prepare ... \n", pre.NodeID)
    //获取消息源节点的公钥,用于数字签名验证
    MessageNodePubKey := p.getPubKey(pre.NodeID)
    digestByte, _ := hex.DecodeString(pre.Digest)
    if _, ok := p.messagePool[pre.Digest]; !ok {
        fmt.Println("当前临时消息池无此摘要,拒绝执行commit广播")
    } else if p.sequenceID != pre.SequenceID {
        fmt.Println("消息序号对不上,拒绝执行commit广播")
    } else if !p.RsaVerySignWithSha256(digestByte, pre.Sign, MessageNodePubKey) {
        fmt.Println("节点签名验证失败!,拒绝执行commit广播")
    } else {
        p.setPrePareConfirmMap(pre.Digest, pre.NodeID, true)
        count := 0
        for range p.prePareConfirmCount[pre.Digest] {
            count++
        }
        //因为主节点不会发送Prepare,所以不包含自己
        specifiedCount := 0
        if p.node.nodeID == "N0" {
            specifiedCount = nodeCount / 3 * 2
        } else {
            specifiedCount = (nodeCount / 3 * 2) - 1
        }
        //如果节点至少收到了2f个prepare的消息(包括自己),并且没有进行过commit广播,则进行commit广播
        p.lock.Lock()
        //获取消息源节点的公钥,用于数字签名验证
        if count >= specifiedCount && !p.isCommitBordcast[pre.Digest] {
            fmt.Println("本节点已收到至少2f个节点(包括本地节点)发来的Prepare信息 ...")
            //节点使用私钥对其签名
            sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey)
            c := Commit{pre.Digest, pre.SequenceID, p.node.nodeID, sign}
            bc, err := json.Marshal(c)
            if err != nil {
                log.Panic(err)
            }
            //进行提交信息的广播
            fmt.Println("正在进行commit广播")
            p.broadcast(cCommit, bc)
            p.isCommitBordcast[pre.Digest] = true
            fmt.Println("commit广播完成")
        }
        p.lock.Unlock()
    }
}

//处理提交确认消息
func (p *pbft) handleCommit(content []byte) {
    //使用json解析出Commit结构体
    c := new(Commit)
    err := json.Unmarshal(content, c)
    if err != nil {
        log.Panic(err)
    }
    fmt.Printf("本节点已接收到%s节点发来的Commit ... \n", c.NodeID)
    //获取消息源节点的公钥,用于数字签名验证
    MessageNodePubKey := p.getPubKey(c.NodeID)
    digestByte, _ := hex.DecodeString(c.Digest)
    if _, ok := p.prePareConfirmCount[c.Digest]; !ok {
        fmt.Println("当前prepare池无此摘要,拒绝将信息持久化到本地消息池")
    } else if p.sequenceID != c.SequenceID {
        fmt.Println("消息序号对不上,拒绝将信息持久化到本地消息池")
    } else if !p.RsaVerySignWithSha256(digestByte, c.Sign, MessageNodePubKey) {
        fmt.Println("节点签名验证失败!,拒绝将信息持久化到本地消息池")
    } else {
        p.setCommitConfirmMap(c.Digest, c.NodeID, true)
        count := 0
        for range p.commitConfirmCount[c.Digest] {
            count++
        }
        //如果节点至少收到了2f+1个commit消息(包括自己),并且节点没有回复过,并且已进行过commit广播,则提交信息至本地消息池,并reply成功标志至客户端!
        p.lock.Lock()
        if count >= nodeCount/3*2 && !p.isReply[c.Digest] && p.isCommitBordcast[c.Digest] {
            fmt.Println("本节点已收到至少2f + 1 个节点(包括本地节点)发来的Commit信息 ...")
            //将消息信息,提交到本地消息池中!
            localMessagePool = append(localMessagePool, p.messagePool[c.Digest].Message)
            info := p.node.nodeID + "节点已将msgid:" + strconv.Itoa(p.messagePool[c.Digest].ID) + "存入本地消息池中,消息内容为:" + p.messagePool[c.Digest].Content
            fmt.Println(info)
            fmt.Println("正在reply客户端 ...")
            tcpDial([]byte(info), p.messagePool[c.Digest].ClientAddr)
            p.isReply[c.Digest] = true
            fmt.Println("reply完毕")
        }
        p.lock.Unlock()
    }
}

//序号累加
func (p *pbft) sequenceIDAdd() {
    p.lock.Lock()
    p.sequenceID++
    p.lock.Unlock()
}

//向除自己外的其他节点进行广播
func (p *pbft) broadcast(cmd command, content []byte) {
    for i := range nodeTable {
        if i == p.node.nodeID {
            continue
        }
        message := jointMessage(cmd, content)
        go tcpDial(message, nodeTable[i])
    }
}

//为多重映射开辟赋值
func (p *pbft) setPrePareConfirmMap(val, val2 string, b bool) {
    if _, ok := p.prePareConfirmCount[val]; !ok {
        p.prePareConfirmCount[val] = make(map[string]bool)
    }
    p.prePareConfirmCount[val][val2] = b
}

//为多重映射开辟赋值
func (p *pbft) setCommitConfirmMap(val, val2 string, b bool) {
    if _, ok := p.commitConfirmCount[val]; !ok {
        p.commitConfirmCount[val] = make(map[string]bool)
    }
    p.commitConfirmCount[val][val2] = b
}

//传入节点编号, 获取对应的公钥
func (p *pbft) getPubKey(nodeID string) []byte {
    key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PUB")
    if err != nil {
        log.Panic(err)
    }
    return key
}

//传入节点编号, 获取对应的私钥
func (p *pbft) getPivKey(nodeID string) []byte {
    key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PIV")
    if err != nil {
        log.Panic(err)
    }
    return key
}

2.5、rsa.go

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/pem"
    "errors"
    "fmt"
    "log"
    "os"
    "strconv"
)

//如果当前目录下不存在目录Keys,则创建目录,并为各个节点生成rsa公私钥
func genRsaKeys() {
    if !isExist("./Keys") {
        fmt.Println("检测到还未生成公私钥目录,正在生成公私钥 ...")
        err := os.Mkdir("Keys", 0644)
        if err != nil {
            log.Panic()
        }
        for i := 0; i <= 4; i++ {
            if !isExist("./Keys/N" + strconv.Itoa(i)) {
                err := os.Mkdir("./Keys/N"+strconv.Itoa(i), 0644)
                if err != nil {
                    log.Panic()
                }
            }
            priv, pub := getKeyPair()
            privFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PIV"
            file, err := os.OpenFile(privFileName, os.O_RDWR|os.O_CREATE, 0644)
            if err != nil {
                log.Panic(err)
            }
            defer file.Close()
            file.Write(priv)

            pubFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PUB"
            file2, err := os.OpenFile(pubFileName, os.O_RDWR|os.O_CREATE, 0644)
            if err != nil {
                log.Panic(err)
            }
            defer file2.Close()
            file2.Write(pub)
        }
        fmt.Println("已为节点们生成RSA公私钥")
    }
}

//生成rsa公私钥
func getKeyPair() (prvkey, pubkey []byte) {
    // 生成私钥文件
    privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
    if err != nil {
        panic(err)
    }
    derStream := x509.MarshalPKCS1PrivateKey(privateKey)
    block := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: derStream,
    }
    prvkey = pem.EncodeToMemory(block)
    publicKey := &privateKey.PublicKey
    derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
    if err != nil {
        panic(err)
    }
    block = &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: derPkix,
    }
    pubkey = pem.EncodeToMemory(block)
    return
}

//判断文件或文件夹是否存在
func isExist(path string) bool {
    _, err := os.Stat(path)
    if err != nil {
        if os.IsExist(err) {
            return true
        }
        if os.IsNotExist(err) {
            return false
        }
        fmt.Println(err)
        return false
    }
    return true
}

//数字签名
func (p *pbft) RsaSignWithSha256(data []byte, keyBytes []byte) []byte {
    h := sha256.New()
    h.Write(data)
    hashed := h.Sum(nil)
    block, _ := pem.Decode(keyBytes)
    if block == nil {
        panic(errors.New("private key error"))
    }
    privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        fmt.Println("ParsePKCS8PrivateKey err", err)
        panic(err)
    }

    signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
    if err != nil {
        fmt.Printf("Error from signing: %s\n", err)
        panic(err)
    }

    return signature
}

//签名验证
func (p *pbft) RsaVerySignWithSha256(data, signData, keyBytes []byte) bool {
    block, _ := pem.Decode(keyBytes)
    if block == nil {
        panic(errors.New("public key error"))
    }
    pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        panic(err)
    }

    hashed := sha256.Sum256(data)
    err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], signData)
    if err != nil {
        panic(err)
    }
    return true
}

2.6、tcp.go

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net"
)

//客户端使用的tcp监听
func clientTcpListen() {
    listen, err := net.Listen("tcp", clientAddr)
    if err != nil {
        log.Panic(err)
    }
    defer listen.Close()

    for {
        conn, err := listen.Accept()
        if err != nil {
            log.Panic(err)
        }
        b, err := ioutil.ReadAll(conn)
        if err != nil {
            log.Panic(err)
        }
        fmt.Println(string(b))
    }

}

//节点使用的tcp监听
func (p *pbft) tcpListen() {
    listen, err := net.Listen("tcp", p.node.addr)
    if err != nil {
        log.Panic(err)
    }
    fmt.Printf("节点开启监听,地址:%s\n", p.node.addr)
    defer listen.Close()

    for {
        conn, err := listen.Accept()
        if err != nil {
            log.Panic(err)
        }
        b, err := ioutil.ReadAll(conn)
        if err != nil {
            log.Panic(err)
        }
        p.handleRequest(b)
    }

}

//使用tcp发送消息
func tcpDial(context []byte, addr string) {
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        log.Println("connect error", err)
        return
    }

    _, err = conn.Write(context)
    if err != nil {
        log.Fatal(err)
    }
    conn.Close()
}
>参考资料:
> - https://www.jianshu.com/p/fb5edf031afd
> -  https://www.cnblogs.com/gexin/p/10242161.html

<br>


本demo为pbft共识算法的代码实现,如果想了解pbft的详细信息请自行浏览参考资料\
本demo展示了pbft的部分功能(没有写主节点轮循机制),写的并不严谨,仅作为对pbft的了解用途


<br>

![在这里插入图片描述](images/流程图.webp)
## 实现功能:
>pbft公式:  n>=3f + 1  其中n为全网总节点数量,f为最多允许的作恶、故障节点


  数据从客户端输入,到接收到节点们的回复共分为5步
  
 1. 客户端向主节点发送请求信息
 2. 主节点N0接收到客户端请求后将请求数据里的主要信息提出,并向其余节点进行preprepare发送
 3. 从节点们接收到来自主节点的preprepare,首先利用主节点的公钥进行签名认证,其次将消息进行散列(消息摘要,以便缩小信息在网络中的传输大小)后,向其他节点广播prepare
 4. 节点接收到2f个prepare信息(包含自己),并全部签名验证通过,则可以进行到commit步骤,向全网其他节点广播commit
 5. 节点接收到2f+1个commit信息(包含自己),并全部签名验证通过,则可以把消息存入到本地,并向客户端返回reply消息

<br>


## 运行步骤:
<br>

##### 1.下载/编译
```shell
 git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git
```
```shell
 cd blockchain_consensus_algorithm/pbft
```
```go
 go build -o pbft.exe
```

##### 2.开启五个端口(一个客户端,四个节点)
客户端执行pbft.exe client  
其他四个节点依次执行 pbft.exe N0  pbft.exe N1  pbft.exe N2  pbft.exe N3
![在这里插入图片描述](images/启动.png)
##### 3.输入一段信息,看看节点之间的同步过程
![在这里插入图片描述](images/启动后.png)
##### 4.关闭一个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply
可以看到,客户端依然会接收到reply,因为根据公式 n >= 3f+1  ,就算宕机一个节点,系统依然能顺利运行
![](images/掉了一个节点后.png)
##### 4.关闭两个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply
可以看到,关闭两个节点后,故障节点已经超出了pbft的允许数量,消息进行到Prepare阶段由于接收不到满足数量的信息,固系统不再进行commit确认,客户端也接收不到reply
![在这里插入图片描述](images/关闭两个节点.png)

>**&ensp;&ensp;&ensp;建了个QQ群:722124200     有问题可以加群互相讨论   :)** \
>**&ensp;&ensp;&ensp;邮箱:mikesen1994@gmail.com  &ensp;&ensp;&ensp; vx:965952482**

pos

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "log"
    "math/big"
    "strconv"
    "time"
)

type block struct {
    //上一个块的hash
    prehash string
    //本块hash
    hash string
    //时间戳
    timestamp string
    //区块内容
    data string
    //区块高度
    height int
    //挖出本块的地址
    address string
}

//用于存储区块链
var blockchain []block

//代表挖矿节点
type node struct {
    //代币数量
    tokens int
    //质押时间
    days int
    //节点地址
    address string
}

//挖矿节点
var mineNodesPool []node

//概率节点池
var probabilityNodesPool []node

//初始化
func init() {
    //手动添加两个节点
    mineNodesPool = append(mineNodesPool, node{1000, 1, "AAAAAAAAAA"})
    mineNodesPool = append(mineNodesPool, node{100, 3, "BBBBBBBBBB"})
    //初始化随机节点池(挖矿概率与代币数量和币龄有关)
    for _, v := range mineNodesPool {
        for i := 0; i <= v.tokens*v.days; i++ {
            probabilityNodesPool = append(probabilityNodesPool, v)
        }
    }
}

//生成新的区块
func generateNewBlock(oldBlock block, data string, address string) block {
    newBlock := block{}
    newBlock.prehash = oldBlock.hash
    newBlock.data = data
    newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05")
    newBlock.height = oldBlock.height + 1
    newBlock.address = getMineNodeAddress()
    newBlock.getHash()
    return newBlock
}

//对自身进行散列
func (b *block) getHash() {
    sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height)
    hash := sha256.Sum256([]byte(sumString))
    b.hash = hex.EncodeToString(hash[:])
}

//随机得出挖矿地址(挖矿概率跟代币数量与币龄有关)
func getMineNodeAddress() string {
    bInt := big.NewInt(int64(len(probabilityNodesPool)))
    //得出一个随机数,最大不超过随机节点池的大小
    rInt, err := rand.Int(rand.Reader, bInt)
    if err != nil {
        log.Panic(err)
    }
    return probabilityNodesPool[int(rInt.Int64())].address
}

func main() {
    //创建创世区块
    genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "0000000000"}
    genesisBlock.getHash()
    //把创世区块添加进区块链
    blockchain = append(blockchain, genesisBlock)
    fmt.Println(blockchain[0])
    i := 0
    for {
        time.Sleep(time.Second)
        newBlock := generateNewBlock(blockchain[i], "我是区块内容", "00000")
        blockchain = append(blockchain, newBlock)
        fmt.Println(blockchain[i+1])
        i++
    }
}
>权益证明机制最开始是由点点币提出并应用(出块概率=代币数量 * 币龄) 简单来说谁的币多,谁就有更大的出块概率。但是深挖下去,这个出块概率谁来计算?碰到无成本利益关系问题怎么办?这个共识算法初看很简单,实际有很多问题需要解决,且看以太坊什么时候能完全转换到POS机制吧

<br>

区块结构
```go
type block struct {
    //上一个块的hash
    prehash string
    //本块hash
    hash string
    //时间戳
    timestamp string
    //区块内容
    data string
    //区块高度
    height int
    //挖出本块的地址
    address string
}
```
声明两个节点池\
mineNodesPool 用来存放指定的挖矿节点\
probabilityNodesPool  用于存入挖矿节点的代币数量*币龄获得的概率
```go
//用于存储区块链
var blockchain []block
//代表挖矿节点
type node struct{
    //代币数量
    tokens int
    //质押时间
    days  int
    //节点地址
    address string
}
//挖矿节点
var mineNodesPool []node
//概率节点池
var  probabilityNodesPool []node
```
初始化节点池:
```go
func init () {
    //手动添加两个节点
    mineNodesPool = append(mineNodesPool,node{1000,1,"AAAAAAAAAA"})
    mineNodesPool = append(mineNodesPool,node{100,3,"BBBBBBBBBB"})
    //初始化随机节点池(挖矿概率与代币数量和币龄有关)
    for _,v:=range mineNodesPool{
        for i:=0;i<=v.tokens * v.days; i ++ {
            randNodesPool = append(randNodesPool,v)
        }
    }
}
```
每次挖矿都会从概率节点池中随机选出获得出块权的节点地址
```go
//随机得出挖矿地址(挖矿概率跟代币数量与币龄有关)
func getMineNodeAddress() string{
    bInt:=big.NewInt(int64(len(randNodesPool)))
    //得出一个随机数,最大不超过随机节点池的大小
    rInt,err:=rand.Int(rand.Reader,bInt)
    if err != nil {
        log.Panic(err)
    }
    return randNodesPool[int(rInt.Int64())].address
}
```


```go
func main() {
    //创建创世区块
    genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000","",time.Now().Format("2006-01-02 15:04:05"),"我是创世区块",1,"0000000000"}
    genesisBlock.getHash()
    //把创世区块添加进区块链
    blockchain = append(blockchain,genesisBlock)
    fmt.Println(blockchain[0])
    i:=0
    for  {
        time.Sleep(time.Second)
        newBlock:=generateNewBlock(blockchain[i],"我是区块内容","00000")
        blockchain = append(blockchain,newBlock)
        fmt.Println(blockchain[i + 1])
        i++
    }
}
```

运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211154915783.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)

pow

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "log"
    "math/big"
    "strconv"
    "time"
)

//用于存放区块,以便连接成区块链
var blockchain []block

//挖矿难度值
var diffNum uint = 17

type block struct {
    //上一个区块的Hash
    Lasthash string
    //本区块Hash
    Hash string
    //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易)
    Data string
    //时间戳
    Timestamp string
    //区块高度
    Height int
    //难度值
    DiffNum uint
    //随机数
    Nonce int64
}

//区块挖矿(通过自身递增nonce值计算hash)
func mine(data string) block {
    if len(blockchain) < 1 {
        log.Panic("还未生成创世区块!")
    }
    lastBlock := blockchain[len(blockchain)-1]
    //制造一个新的区块
    newBlock := new(block)
    newBlock.Lasthash = lastBlock.Hash
    newBlock.Timestamp = time.Now().String()
    newBlock.Height = lastBlock.Height + 1
    newBlock.DiffNum = diffNum
    newBlock.Data = data
    var nonce int64 = 0
    //根据挖矿难度值计算的一个大数
    newBigint := big.NewInt(1)
    newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum
    for {
        newBlock.Nonce = nonce
        newBlock.getHash()
        hashInt := big.Int{}
        hashBytes, _ := hex.DecodeString(newBlock.Hash)
        hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字
        //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功
        if hashInt.Cmp(newBigint) == -1 {
            break
        } else {
            nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数
        }
    }
    return *newBlock
}

func (b *block) serialize() []byte {
    bytes, err := json.Marshal(b)
    if err != nil {
        log.Panic(err)
    }
    return bytes
}

func (b *block) getHash() {
    result := sha256.Sum256(b.serialize())
    b.Hash = hex.EncodeToString(result[:])
}

func main() {
    //制造一个创世区块
    genesisBlock := new(block)
    genesisBlock.Timestamp = time.Now().String()
    genesisBlock.Data = "我是创世区块!"
    genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000"
    genesisBlock.Height = 1
    genesisBlock.Nonce = 0
    genesisBlock.DiffNum = 0
    genesisBlock.getHash()
    fmt.Println(*genesisBlock)
    //将创世区块添加进区块链
    blockchain = append(blockchain, *genesisBlock)
    for i := 0; i < 10; i++ {
        newBlock := mine("天气不错" + strconv.Itoa(i))
        blockchain = append(blockchain, newBlock)
        fmt.Println(newBlock)
    }
}
<br>



>工作量证明机制的核心在于不断hash区块自身,将hash值与根据难度值计算出的一串大数对比,如果自身hash小于大数则说明挖矿成功,否则变化自身随机数重新计算。并且程序会随着出块间隔时间动态调节难度值(比如比特币)

<br>

区块结构
```go
type block struct {
    //上一个区块的Hash
    Lasthash string
    //本区块Hash
    Hash string
    //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易)
    Data string
    //时间戳
    Timestamp string
    //区块高度
    Height int
    //难度值
    DiffNum uint
    //随机数
    Nonce int64
}
```
挖矿函数:\
使用math/big包,根据全局变量的难度值diffNum计算出用于实际比较的一串大数newBigint ,并同时将区块hash转换为大数hashInt   两个大数进行数值比较,如果hashInt小于newBigint 则代表挖矿成功

```go
//区块挖矿(通过自身递增nonce值计算hash)
func mine(data string) block {
    if len(blockchain) < 1 {
        log.Panic("还未生成创世区块!")
    }
    lastBlock := blockchain[len(blockchain)-1]
    //制造一个新的区块
    newBlock := new(block)
    newBlock.Lasthash = lastBlock.Hash
    newBlock.Timestamp = time.Now().String()
    newBlock.Height = lastBlock.Height + 1
    newBlock.DiffNum = diffNum
    newBlock.Data = data
    var nonce int64 = 0
    //根据挖矿难度值计算的一个大数
    newBigint := big.NewInt(1)
    newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum
    for {
        newBlock.Nonce = nonce
        newBlock.getHash()
        hashInt := big.Int{}
        hashBytes, _ := hex.DecodeString(newBlock.Hash)
        hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字
        //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功
        if hashInt.Cmp(newBigint) == -1 {
            break
        } else {
            nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数
        }
    }
    return *newBlock
}
```

```go
func main() {
    //制造一个创世区块
    genesisBlock := new(block)
    genesisBlock.Timestamp = time.Now().String()
    genesisBlock.Data = "我是创世区块!"
    genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000"
    genesisBlock.Height = 1
    genesisBlock.Nonce = 0
    genesisBlock.DiffNum = 0
    genesisBlock.getHash()
    fmt.Println(*genesisBlock)
    //将创世区块添加进区块链
    blockchain = append(blockchain, *genesisBlock)
    for i := 0; i < 10; i++ {
        newBlock := mine("天气不错"+strconv.Itoa(i))
        blockchain = append(blockchain, newBlock)
        fmt.Println(newBlock)
    }
```

运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211145732513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70)

raft

5.1、http.go

package main

import (
    "crypto/rand"
    "fmt"
    "log"
    "math/big"
    "net/http"
    "net/rpc"
)

//等待节点访问
func (rf *Raft) getRequest(writer http.ResponseWriter, request *http.Request) {
    request.ParseForm()
    //http://localhost:8080/req?message=ohmygod
    if len(request.Form["message"]) > 0 && rf.currentLeader != "-1" {
        message := request.Form["message"][0]
        m := new(Message)
        m.MsgID = getRandom()
        m.Msg = message
        //接收到消息后,直接转发到领导者
        fmt.Println("http监听到了消息,准备发送给领导者,消息id:", m.MsgID)
        port := nodeTable[rf.currentLeader]
        rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port)
        if err != nil {
            log.Panic(err)
        }
        b := false
        err = rp.Call("Raft.LeaderReceiveMessage", m, &b)
        if err != nil {
            log.Panic(err)
        }
        fmt.Println("消息是否已发送到领导者:", b)
        writer.Write([]byte("ok!!!"))
    }
}

func (rf *Raft) httpListen() {
    //创建getRequest()回调方法
    http.HandleFunc("/req", rf.getRequest)
    fmt.Println("监听8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println(err)
        return
    }
}

//返回一个十位数的随机数,作为消息idgit
func getRandom() int {
    x := big.NewInt(10000000000)
    for {
        result, err := rand.Int(rand.Reader, x)
        if err != nil {
            log.Panic(err)
        }
        if result.Int64() > 1000000000 {
            return int(result.Int64())
        }
    }
}

5.2、main.go

package main

import (
    "fmt"
    "log"
    "os"
    "time"
)

//定义节点数量
var raftCount = 3

//节点池
var nodeTable map[string]string

//选举超时时间(单位:秒)
var timeout = 3

//心跳检测超时时间
var heartBeatTimeout = 7

//心跳检测频率(单位:秒)
var heartBeatTimes = 3

//用于存储消息
var MessageStore = make(map[int]string)

func main() {
    //定义三个节点  节点编号 - 监听端口号
    nodeTable = map[string]string{
        "A": ":9000",
        "B": ":9001",
        "C": ":9002",
    }
    //运行程序时候 指定节点编号
    if len(os.Args) < 1 {
        log.Fatal("程序参数不正确")
    }

    id := os.Args[1]
    //传入节点编号,端口号,创建raft实例
    raft := NewRaft(id, nodeTable[id])
    //启用RPC,注册raft
    go rpcRegister(raft)
    //开启心跳检测
    go raft.heartbeat()
    //开启一个Http监听
    if id == "A" {
        go raft.httpListen()
    }

Circle:
    //开启选举
    go func() {
        for {
            //成为候选人节点
            if raft.becomeCandidate() {
                //成为后选人节点后 向其他节点要选票来进行选举
                if raft.election() {
                    break
                } else {
                    continue
                }
            } else {
                break
            }
        }
    }()

    //进行心跳检测
    for {
        //0.5秒检测一次
        time.Sleep(time.Millisecond * 5000)
        if raft.lastHeartBeartTime != 0 && (millisecond()-raft.lastHeartBeartTime) > int64(raft.timeout*1000) {
            fmt.Printf("心跳检测超时,已超过%d秒\n", raft.timeout)
            fmt.Println("即将重新开启选举")
            raft.reDefault()
            raft.setCurrentLeader("-1")
            raft.lastHeartBeartTime = 0
            goto Circle
        }
    }
}

5.3、raft.go

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

//声明raft节点类型
type Raft struct {
    node *NodeInfo
    //本节点获得的投票数
    vote int
    //线程锁
    lock sync.Mutex
    //节点编号
    me string
    //当前任期
    currentTerm int
    //为哪个节点投票
    votedFor string
    //当前节点状态
    //0 follower  1 candidate  2 leader
    state int
    //发送最后一条消息的时间
    lastMessageTime int64
    //发送最后一条消息的时间
    lastHeartBeartTime int64
    //当前节点的领导
    currentLeader string
    //心跳超时时间(单位:秒)
    timeout int
    //接收投票成功通道
    voteCh chan bool
    //心跳信号
    heartBeat chan bool
}

type NodeInfo struct {
    ID   string
    Port string
}

type Message struct {
    Msg   string
    MsgID int
}

func NewRaft(id, port string) *Raft {
    node := new(NodeInfo)
    node.ID = id
    node.Port = port

    rf := new(Raft)
    //节点信息
    rf.node = node
    //当前节点获得票数
    rf.setVote(0)
    //编号
    rf.me = id
    //给0  1  2三个节点投票,给谁都不投
    rf.setVoteFor("-1")
    //0 follower
    rf.setStatus(0)
    //最后一次心跳检测时间
    rf.lastHeartBeartTime = 0
    rf.timeout = heartBeatTimeout
    //最初没有领导
    rf.setCurrentLeader("-1")
    //设置任期
    rf.setTerm(0)
    //投票通道
    rf.voteCh = make(chan bool)
    //心跳通道
    rf.heartBeat = make(chan bool)
    return rf
}

//修改节点为候选人状态
func (rf *Raft) becomeCandidate() bool {
    r := randRange(1500, 5000)
    //休眠随机时间后,再开始成为候选人
    time.Sleep(time.Duration(r) * time.Millisecond)
    //如果发现本节点已经投过票,或者已经存在领导者,则不用变身候选人状态
    if rf.state == 0 && rf.currentLeader == "-1" && rf.votedFor == "-1" {
        //将节点状态变为1
        rf.setStatus(1)
        //设置为哪个节点投票
        rf.setVoteFor(rf.me)
        //节点任期加1
        rf.setTerm(rf.currentTerm + 1)
        //当前没有领导
        rf.setCurrentLeader("-1")
        //为自己投票
        rf.voteAdd()
        fmt.Println("本节点已变更为候选人状态")
        fmt.Printf("当前得票数:%d\n", rf.vote)
        //开启选举通道
        return true
    } else {
        return false
    }
}

//进行选举
func (rf *Raft) election() bool {
    fmt.Println("开始进行领导者选举,向其他节点进行广播")
    go rf.broadcast("Raft.Vote", rf.node, func(ok bool) {
        rf.voteCh <- ok
    })
    for {
        select {
        case <-time.After(time.Second * time.Duration(timeout)):
            fmt.Println("领导者选举超时,节点变更为追随者状态\n")
            rf.reDefault()
            return false
        case ok := <-rf.voteCh:
            if ok {
                rf.voteAdd()
                fmt.Printf("获得来自其他节点的投票,当前得票数:%d\n", rf.vote)
            }
            if rf.vote > raftCount/2 && rf.currentLeader == "-1" {
                fmt.Println("获得超过网络节点二分之一的得票数,本节点被选举成为了leader")
                //节点状态变为2,代表leader
                rf.setStatus(2)
                //当前领导者为自己
                rf.setCurrentLeader(rf.me)
                fmt.Println("向其他节点进行广播...")
                go rf.broadcast("Raft.ConfirmationLeader", rf.node, func(ok bool) {
                    fmt.Println(ok)
                })
                //开启心跳检测通道
                rf.heartBeat <- true
                return true
            }
        }
    }
}

//心跳检测方法
func (rf *Raft) heartbeat() {
    //如果收到通道开启的信息,将会向其他节点进行固定频率的心跳检测
    if <-rf.heartBeat {
        for {
            fmt.Println("本节点开始发送心跳检测...")
            rf.broadcast("Raft.HeartbeatRe", rf.node, func(ok bool) {
                fmt.Println("收到回复:", ok)
            })
            rf.lastHeartBeartTime = millisecond()
            time.Sleep(time.Second * time.Duration(heartBeatTimes))
        }
    }
}

//产生随机值
func randRange(min, max int64) int64 {
    //用于心跳信号的时间
    rand.Seed(time.Now().UnixNano())
    return rand.Int63n(max-min) + min
}

//获取当前时间的毫秒数
func millisecond() int64 {
    return time.Now().UnixNano() / int64(time.Millisecond)
}

//设置任期
func (rf *Raft) setTerm(term int) {
    rf.lock.Lock()
    rf.currentTerm = term
    rf.lock.Unlock()
}

//设置为谁投票
func (rf *Raft) setVoteFor(id string) {
    rf.lock.Lock()
    rf.votedFor = id
    rf.lock.Unlock()
}

//设置当前领导者
func (rf *Raft) setCurrentLeader(leader string) {
    rf.lock.Lock()
    rf.currentLeader = leader
    rf.lock.Unlock()
}

//设置当前领导者
func (rf *Raft) setStatus(state int) {
    rf.lock.Lock()
    rf.state = state
    rf.lock.Unlock()
}

//投票累加
func (rf *Raft) voteAdd() {
    rf.lock.Lock()
    rf.vote++
    rf.lock.Unlock()
}

//设置投票数量
func (rf *Raft) setVote(num int) {
    rf.lock.Lock()
    rf.vote = num
    rf.lock.Unlock()
}

//恢复默认设置
func (rf *Raft) reDefault() {
    rf.setVote(0)
    //rf.currentLeader = "-1"
    rf.setVoteFor("-1")
    rf.setStatus(0)
}

5.4、rpc.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/rpc"
    "time"
)

//rpc服务注册
func rpcRegister(raft *Raft) {
    //注册一个服务器
    err := rpc.Register(raft)
    if err != nil {
        log.Panic(err)
    }
    port := raft.node.Port
    //把服务绑定到http协议上
    rpc.HandleHTTP()
    //监听端口
    err = http.ListenAndServe(port, nil)
    if err != nil {
        fmt.Println("注册rpc服务失败", err)
    }
}

func (rf *Raft) broadcast(method string, args interface{}, fun func(ok bool)) {
    //设置不要自己给自己广播
    for nodeID, port := range nodeTable {
        if nodeID == rf.node.ID {
            continue
        }
        //连接远程rpc
        rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port)
        if err != nil {
            fun(false)
            continue
        }

        var bo = false
        err = rp.Call(method, args, &bo)
        if err != nil {
            fun(false)
            continue
        }
        fun(bo)
    }
}

//投票
func (rf *Raft) Vote(node NodeInfo, b *bool) error {
    if rf.votedFor != "-1" || rf.currentLeader != "-1" {
        *b = false
    } else {
        rf.setVoteFor(node.ID)
        fmt.Printf("投票成功,已投%s节点\n", node.ID)
        *b = true
    }
    return nil
}

//确认领导者
func (rf *Raft) ConfirmationLeader(node NodeInfo, b *bool) error {
    rf.setCurrentLeader(node.ID)
    *b = true
    fmt.Println("已发现网络中的领导节点,", node.ID, "成为了领导者!\n")
    rf.reDefault()
    return nil
}

//心跳检测回复
func (rf *Raft) HeartbeatRe(node NodeInfo, b *bool) error {
    rf.setCurrentLeader(node.ID)
    rf.lastHeartBeartTime = millisecond()
    fmt.Printf("接收到来自领导节点%s的心跳检测\n", node.ID)
    fmt.Printf("当前时间为:%d\n\n", millisecond())
    *b = true
    return nil
}

//追随者节点用来接收消息,然后存储到消息池中,待领导者确认后打印
func (rf *Raft) ReceiveMessage(message Message, b *bool) error {
    fmt.Printf("接收到领导者节点发来的信息,id为:%d\n", message.MsgID)
    MessageStore[message.MsgID] = message.Msg
    *b = true
    fmt.Println("已回复接收到消息,待领导者确认后打印")
    return nil
}

//追随者节点的反馈得到领导者节点的确认,开始打印消息
func (rf *Raft) ConfirmationMessage(message Message, b *bool) error {
    go func() {
        for {
            if _, ok := MessageStore[message.MsgID]; ok {
                fmt.Printf("raft验证通过,可以打印消息,id为:%d\n", message.MsgID)
                fmt.Println("消息为:", MessageStore[message.MsgID], "\n")
                rf.lastMessageTime = millisecond()
                break
            } else {
                //如果没有此消息,等一会看看!!!
                time.Sleep(time.Millisecond * 10)
            }

        }
    }()
    *b = true
    return nil
}

//领导者接收到,追随者节点转发过来的消息
func (rf *Raft) LeaderReceiveMessage(message Message, b *bool) error {
    fmt.Printf("领导者节点接收到转发过来的消息,id为:%d\n", message.MsgID)
    MessageStore[message.MsgID] = message.Msg
    *b = true
    fmt.Println("准备将消息进行广播...")
    num := 0
    go rf.broadcast("Raft.ReceiveMessage", message, func(ok bool) {
        if ok {
            num++
        }
    })

    for {
        //自己默认收到了消息,所以减去一
        if num > raftCount/2-1 {
            fmt.Printf("全网已超过半数节点接收到消息id:%d\nraft验证通过,可以打印消息,id为:%d\n", message.MsgID, message.MsgID)
            fmt.Println("消息为:", MessageStore[message.MsgID], "\n")
            rf.lastMessageTime = millisecond()
            fmt.Println("准备将消息提交信息发送至客户端...")
            go rf.broadcast("Raft.ConfirmationMessage", message, func(ok bool) {
            })
            break
        } else {
            //休息会儿
            time.Sleep(time.Millisecond * 100)
        }
    }
    return nil
}
>**&ensp;&ensp;&ensp;邮箱:mikesen1994@gmail.com   &ensp;&ensp;&ensp;&ensp;  &ensp;&ensp; &ensp;&ensp; vx:965952482**

<hr>


本demo为raft的代码实现,如果想了解raft的详细信息请自行浏览参考资料<br>
本demo展示了raft的部分功能,写的并不严谨,仅作为对raft的了解用途
<br>

## 实现功能:

 - 节点状态分为Leader(领导者)、Follower(追随者)、Candidate(候选人)
 - 节点间随机成为candidate状态并选举出Leader,且同时仅存在一个Leader
 - Leader节点定时发送心跳检测至其他Follower节点
 - Follower节点们超过一定时间未收到心跳检测,则Follower节点们重新开启选举
 - 客户端通过http发送消息到节点A,如果A不是Leader则转发至Leader节点
 - Leader收到客户端的消息后向Follower节点进行广播
 - Follower节点收到消息,反馈给Leader,等待Leader确认
 - Leader收到全网超过二分之一的反馈后,本地进行打印,然后将确认收到反馈的信息提交到Follower节点
 - Follower节点收到确认提交信息后,打印消息

<br>

## 运行步骤:
<br>

##### 1.下载/编译
```shell
 git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git
```
```shell
 cd blockchain_consensus_algorithm/raft
```
```go
 go build -o raft.exe
```

##### 2.开启三个端口,并分别执行raft.exe A 、raft.exe B 、 raft.exe C,代表开启三个节点(初始状态为追随者)
![在这里插入图片描述](images/开启端口.png)

##### 3.三个节点会随机选举出领导者(其中A节点默认监听来自http的访问),成功的节点会发送心跳检测到其他两个节点
![在这里插入图片描述](images/选举成功.png)
##### 4.此时打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,比如:
`http://localhost:8080/req?message=噢,我的上帝呀`
![在这里插入图片描述](images/打印消息.png)
可以看到三个节点同时打印了消息,本段数据同步步骤可以用下图进行理解(不过缺少了4.1步骤)
![在这里插入图片描述](images/消息同步.png)
##### 5.如果领导者节点宕机了怎么办呢,我们尝试关闭领导者节点B
![在这里插入图片描述](images/领导者节点宕机.png)
可以发现关闭领导者B后,节点间有个超时机制,如果超过一定时间没有收到心跳检测,则会自动开始重新进行选举,此时A当选了新的领导者

##### 6.再次打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,看看还能同步打印吗
`http://localhost:8080/req?message=天气不错`
![在这里插入图片描述](images/残缺打印.png)
结果发现可以打印的,因为新的领导者A、追随者C并没有宕机,A收到了C的回馈(2>3/2)超过了全网一半的节点,所以是可以进行打印数据的

##### 7.重新启动节点B,B自动变为追随者状态,并接收来自新的领导者A的心跳检测
![在这里插入图片描述](images/重启B.png)
<hr>

>参考资料:
> - http://thesecretlivesofdata.com/raft/
> - https://www.cnblogs.com/mindwind/p/5231986.html
> - https://blog.csdn.net/s15738841819/article/details/84286276

 

posted @ 2020-06-14 20:37  将王相  阅读(245)  评论(0编辑  收藏  举报