├── README.md ├── dpos ├── README.md └── main.go ├── pbft ├── README.md ├── client.go ├── cmd.go ├── images │ ├── 关闭两个节点.png │ ├── 启动.png │ ├── 启动后.png │ ├── 掉了一个节点后.png │ └── 流程图.webp ├── main.go ├── pbft.go ├── rsa.go └── tcp.go ├── pos ├── README.md └── main.go ├── pow ├── README.md └── main.go └── raft ├── README.md ├── http.go ├── images ├── 开启端口.png ├── 打印消息.png ├── 残缺打印.png ├── 消息同步.png ├── 选举成功.png ├── 重启B.png └── 领导者节点宕机.png ├── main.go ├── raft.go └── rpc.go /README.md: -------------------------------------------------------------------------------- 1 | 2 | >近期学习总结并代码实现的五大区块链共识算法,如果发现bug或者理解方面的错误请不吝指教 :) 3 | 4 | 5 | #### POW(工作量证明) 6 | https://github.com/corgi-kx/blockchain_consensus_algorithm/tree/master/pow 7 | #### POS(权益证明) 8 | https://github.com/corgi-kx/blockchain_consensus_algorithm/tree/master/pos 9 | #### DPOS(股份授权证明) 10 | https://github.com/corgi-kx/blockchain_consensus_algorithm/tree/master/dpos 11 | #### PBFT(实用拜占庭) 12 | https://github.com/corgi-kx/blockchain_consensus_algorithm/tree/master/pbft 13 | #### RAFT 14 | https://github.com/corgi-kx/blockchain_consensus_algorithm/tree/master/raft 15 | 16 | -------------------------------------------------------------------------------- /dpos/README.md: -------------------------------------------------------------------------------- 1 | 2 | >股份授权证明机制是POS的一个变种,简单来说就是你手里有选票(币就相当于选票)。这时一些正在竞选超级节点的大节点们说把票都投给我把,等我当选了超级节点,我吃肉你喝汤,岂不美哉?然后你就信了,把票投给了竞选节点,这些节点竞选成功成为超级节点后会轮循的获得出块权。旷工费、通胀放出的代币也就都到了他们手里了。比较中心化的一种共识机制,但是TPS很高。 3 | 4 |
5 | 6 | 区块结构: 7 | ```go 8 | type block struct { 9 | //上一个块的hash 10 | prehash string 11 | //本块hash 12 | hash string 13 | //时间戳 14 | timestamp string 15 | //区块内容 16 | data string 17 | //区块高度 18 | height int 19 | //挖出本块的节点地址 20 | address string 21 | } 22 | ``` 23 | 24 | ```go 25 | //用于存储区块链 26 | var blockchain []block 27 | //普通节点 28 | type node struct{ 29 | //代币数量 30 | votes int 31 | //节点地址 32 | address string 33 | } 34 | //竞选节点 35 | type superNode struct { 36 | node 37 | } 38 | //投票节点池 39 | var voteNodesPool []node 40 | //竞选节点池 41 | var starNodesPool []superNode 42 | //存放可以挖矿的超级节点池 43 | var superStarNodesPool []superNode 44 | ``` 45 | 46 | 初始化:\ 47 | 生成投票节点池并随机赋予代币数量,同时为竞选节点池生成竞选节点 48 | ```go 49 | //初始化 50 | func init() { 51 | //初始化投票节点 52 | for i:=0;i<=voteNodeNum;i++ { 53 | rInt,err:=rand.Int(rand.Reader,big.NewInt(10000)) 54 | if err != nil { 55 | log.Panic(err) 56 | } 57 | voteNodesPool = append(voteNodesPool,node{int(rInt.Int64()),"投票节点"+strconv.Itoa(i)}) 58 | } 59 | //初始化超级节点 60 | for i:=0;i<=superNodeNum;i++ { 61 | starNodesPool = append(starNodesPool,superNode{node{0,"超级节点"+strconv.Itoa(i)}}) 62 | } 63 | } 64 | ``` 65 | 66 | 67 | 模拟普通节点投票(随机的对竞选节点投票) 68 | ```go 69 | //投票 70 | func voting() { 71 | for _, v := range voteNodesPool { 72 | rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1)) 73 | if err != nil { 74 | log.Panic(err) 75 | } 76 | starNodesPool[int(rInt.Int64())].votes += v.votes 77 | } 78 | } 79 | ``` 80 | 81 | 对竞选节点根据得票数排序,前几名成为超级节点 82 | ```go 83 | //对挖矿节点进行排序 84 | func sortMineNodes() { 85 | sort.Slice(starNodesPool, func(i, j int) bool { 86 | return starNodesPool[i].votes > starNodesPool[j].votes 87 | }) 88 | superStarNodesPool = starNodesPool[:mineSuperNodeNum] 89 | } 90 | ``` 91 | 92 | 主函数 93 | ```go 94 | func main() { 95 | fmt.Println("初始化", voteNodeNum, "个投票节点...") 96 | fmt.Println(voteNodesPool) 97 | fmt.Println("当前存在的", superNodeNum, "个竞选节点") 98 | fmt.Println(starNodesPool) 99 | fmt.Println("投票节点们开始进行投票...") 100 | voting() 101 | fmt.Println("结束投票,查看竞选节点们获得票数...") 102 | fmt.Println(starNodesPool) 103 | fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点") 104 | sortMineNodes() 105 | fmt.Println(superStarNodesPool) 106 | fmt.Println("开始挖矿...") 107 | genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"} 108 | genesisBlock.getHash() 109 | blockchain = append(blockchain, genesisBlock) 110 | fmt.Println(blockchain[0]) 111 | i, j := 0, 0 112 | for { 113 | time.Sleep(time.Second) 114 | newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address) 115 | blockchain = append(blockchain, newBlock) 116 | fmt.Println(blockchain[i+1]) 117 | i++ 118 | j++ 119 | j = j % len(superStarNodesPool) 120 | } 121 | } 122 | ``` 123 | 124 | 运行结果: 125 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211162452121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70) 126 | -------------------------------------------------------------------------------- /dpos/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "sort" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | const ( 16 | voteNodeNum = 100 17 | superNodeNum = 10 18 | mineSuperNodeNum = 3 19 | ) 20 | 21 | type block struct { 22 | //上一个块的hash 23 | prehash string 24 | //本块hash 25 | hash string 26 | //时间戳 27 | timestamp string 28 | //区块内容 29 | data string 30 | //区块高度 31 | height int 32 | //挖出本块的节点地址 33 | address string 34 | } 35 | 36 | //用于存储区块链 37 | var blockchain []block 38 | 39 | //普通节点 40 | type node struct { 41 | //代币数量 42 | votes int 43 | //节点地址 44 | address string 45 | } 46 | 47 | //竞选节点 48 | type superNode struct { 49 | node 50 | } 51 | 52 | //投票节点池 53 | var voteNodesPool []node 54 | 55 | //竞选节点池 56 | var starNodesPool []superNode 57 | 58 | //存放可以挖矿的超级节点池 59 | var superStarNodesPool []superNode 60 | 61 | //生成新的区块 62 | func generateNewBlock(oldBlock block, data string, address string) block { 63 | newBlock := block{} 64 | newBlock.prehash = oldBlock.hash 65 | newBlock.data = data 66 | newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05") 67 | newBlock.height = oldBlock.height + 1 68 | newBlock.address = address 69 | newBlock.getHash() 70 | return newBlock 71 | } 72 | 73 | //对自身进行散列 74 | func (b *block) getHash() { 75 | sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height) 76 | hash := sha256.Sum256([]byte(sumString)) 77 | b.hash = hex.EncodeToString(hash[:]) 78 | } 79 | 80 | //投票 81 | func voting() { 82 | for _, v := range voteNodesPool { 83 | rInt, err := rand.Int(rand.Reader, big.NewInt(superNodeNum+1)) 84 | if err != nil { 85 | log.Panic(err) 86 | } 87 | starNodesPool[int(rInt.Int64())].votes += v.votes 88 | } 89 | } 90 | 91 | //对挖矿节点进行排序 92 | func sortMineNodes() { 93 | sort.Slice(starNodesPool, func(i, j int) bool { 94 | return starNodesPool[i].votes > starNodesPool[j].votes 95 | }) 96 | superStarNodesPool = starNodesPool[:mineSuperNodeNum] 97 | } 98 | 99 | //初始化 100 | func init() { 101 | //初始化投票节点 102 | for i := 0; i <= voteNodeNum; i++ { 103 | rInt, err := rand.Int(rand.Reader, big.NewInt(10000)) 104 | if err != nil { 105 | log.Panic(err) 106 | } 107 | voteNodesPool = append(voteNodesPool, node{int(rInt.Int64()), "投票节点" + strconv.Itoa(i)}) 108 | } 109 | //初始化竞选节点 110 | for i := 0; i <= superNodeNum; i++ { 111 | starNodesPool = append(starNodesPool, superNode{node{0, "超级节点" + strconv.Itoa(i)}}) 112 | } 113 | } 114 | 115 | func main() { 116 | fmt.Println("初始化", voteNodeNum, "个投票节点...") 117 | fmt.Println(voteNodesPool) 118 | fmt.Println("当前存在的", superNodeNum, "个竞选节点") 119 | fmt.Println(starNodesPool) 120 | fmt.Println("投票节点们开始进行投票...") 121 | voting() 122 | fmt.Println("结束投票,查看竞选节点们获得票数...") 123 | fmt.Println(starNodesPool) 124 | fmt.Println("对竞选节点按获得票数排序,前", mineSuperNodeNum, "名,当选超级节点") 125 | sortMineNodes() 126 | fmt.Println(superStarNodesPool) 127 | fmt.Println("开始挖矿...") 128 | genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "000000000"} 129 | genesisBlock.getHash() 130 | blockchain = append(blockchain, genesisBlock) 131 | fmt.Println(blockchain[0]) 132 | i, j := 0, 0 133 | for { 134 | time.Sleep(time.Second) 135 | newBlock := generateNewBlock(blockchain[i], "我是区块内容", superStarNodesPool[j].address) 136 | blockchain = append(blockchain, newBlock) 137 | fmt.Println(blockchain[i+1]) 138 | i++ 139 | j++ 140 | j = j % len(superStarNodesPool) //超级节点轮循获得出块权 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /pbft/README.md: -------------------------------------------------------------------------------- 1 | >参考资料: 2 | > - https://www.jianshu.com/p/fb5edf031afd 3 | > - https://www.cnblogs.com/gexin/p/10242161.html 4 | 5 |
6 | 7 | 8 | 本demo为pbft共识算法的代码实现,如果想了解pbft的详细信息请自行浏览参考资料\ 9 | 本demo展示了pbft的部分功能(没有写主节点轮循机制),写的并不严谨,仅作为对pbft的了解用途 10 | 11 | 12 |
13 | 14 | ![在这里插入图片描述](images/流程图.webp) 15 | ## 实现功能: 16 | >pbft公式: n>=3f + 1 其中n为全网总节点数量,f为最多允许的作恶、故障节点 17 | 18 | 19 | 数据从客户端输入,到接收到节点们的回复共分为5步 20 | 21 | 1. 客户端向主节点发送请求信息 22 | 2. 主节点N0接收到客户端请求后将请求数据里的主要信息提出,并向其余节点进行preprepare发送 23 | 3. 从节点们接收到来自主节点的preprepare,首先利用主节点的公钥进行签名认证,其次将消息进行散列(消息摘要,以便缩小信息在网络中的传输大小)后,向其他节点广播prepare 24 | 4. 节点接收到2f个prepare信息(包含自己),并全部签名验证通过,则可以进行到commit步骤,向全网其他节点广播commit 25 | 5. 节点接收到2f+1个commit信息(包含自己),并全部签名验证通过,则可以把消息存入到本地,并向客户端返回reply消息 26 | 27 |
28 | 29 | 30 | ## 运行步骤: 31 |
32 | 33 | ##### 1.下载/编译 34 | ```shell 35 | git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git 36 | ``` 37 | ```shell 38 | cd blockchain_consensus_algorithm/pbft 39 | ``` 40 | ```go 41 | go build -o pbft.exe 42 | ``` 43 | 44 | ##### 2.开启五个端口(一个客户端,四个节点) 45 | 客户端执行pbft.exe client 46 | 其他四个节点依次执行 pbft.exe N0 pbft.exe N1 pbft.exe N2 pbft.exe N3 47 | ![在这里插入图片描述](images/启动.png) 48 | ##### 3.输入一段信息,看看节点之间的同步过程 49 | ![在这里插入图片描述](images/启动后.png) 50 | ##### 4.关闭一个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply 51 | 可以看到,客户端依然会接收到reply,因为根据公式 n >= 3f+1 ,就算宕机一个节点,系统依然能顺利运行 52 | ![](images/掉了一个节点后.png) 53 | ##### 4.关闭两个节点(代表作恶、故障节点),再次输入信息,看看是否还会接收到reply 54 | 可以看到,关闭两个节点后,故障节点已经超出了pbft的允许数量,消息进行到Prepare阶段由于接收不到满足数量的信息,固系统不再进行commit确认,客户端也接收不到reply 55 | ![在这里插入图片描述](images/关闭两个节点.png) 56 | 57 | >**   建了个QQ群:722124200 有问题可以加群互相讨论 :)** \ 58 | >**   邮箱:mikesen1994@gmail.com     vx:965952482** 59 | -------------------------------------------------------------------------------- /pbft/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "os" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func clientSendMessageAndListen() { 16 | //开启客户端的本地监听(主要用来接收节点的reply信息) 17 | go clientTcpListen() 18 | fmt.Printf("客户端开启监听,地址:%s\n", clientAddr) 19 | 20 | fmt.Println(" ---------------------------------------------------------------------------------") 21 | fmt.Println("| 已进入PBFT测试Demo客户端,请启动全部节点后再发送消息! :) |") 22 | fmt.Println(" ---------------------------------------------------------------------------------") 23 | fmt.Println("请在下方输入要存入节点的信息:") 24 | //首先通过命令行获取用户输入 25 | stdReader := bufio.NewReader(os.Stdin) 26 | for { 27 | data, err := stdReader.ReadString('\n') 28 | if err != nil { 29 | fmt.Println("Error reading from stdin") 30 | panic(err) 31 | } 32 | r := new(Request) 33 | r.Timestamp = time.Now().UnixNano() 34 | r.ClientAddr = clientAddr 35 | r.Message.ID = getRandom() 36 | //消息内容就是用户的输入 37 | r.Message.Content = strings.TrimSpace(data) 38 | br, err := json.Marshal(r) 39 | if err != nil { 40 | log.Panic(err) 41 | } 42 | fmt.Println(string(br)) 43 | content := jointMessage(cRequest, br) 44 | //默认N0为主节点,直接把请求信息发送至N0 45 | tcpDial(content, nodeTable["N0"]) 46 | } 47 | } 48 | 49 | //返回一个十位数的随机数,作为msgid 50 | func getRandom() int { 51 | x := big.NewInt(10000000000) 52 | for { 53 | result, err := rand.Int(rand.Reader, x) 54 | if err != nil { 55 | log.Panic(err) 56 | } 57 | if result.Int64() > 1000000000 { 58 | return int(result.Int64()) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pbft/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "log" 8 | ) 9 | 10 | // 11 | type Request struct { 12 | Message 13 | Timestamp int64 14 | //相当于clientID 15 | ClientAddr string 16 | } 17 | 18 | //<,m> 19 | type PrePrepare struct { 20 | RequestMessage Request 21 | Digest string 22 | SequenceID int 23 | Sign []byte 24 | } 25 | 26 | // 27 | type Prepare struct { 28 | Digest string 29 | SequenceID int 30 | NodeID string 31 | Sign []byte 32 | } 33 | 34 | // 35 | type Commit struct { 36 | Digest string 37 | SequenceID int 38 | NodeID string 39 | Sign []byte 40 | } 41 | 42 | // 43 | type Reply struct { 44 | MessageID int 45 | NodeID string 46 | Result bool 47 | } 48 | 49 | type Message struct { 50 | Content string 51 | ID int 52 | } 53 | 54 | const prefixCMDLength = 12 55 | 56 | type command string 57 | 58 | const ( 59 | cRequest command = "request" 60 | cPrePrepare command = "preprepare" 61 | cPrepare command = "prepare" 62 | cCommit command = "commit" 63 | ) 64 | 65 | //默认前十二位为命令名称 66 | func jointMessage(cmd command, content []byte) []byte { 67 | b := make([]byte, prefixCMDLength) 68 | for i, v := range []byte(cmd) { 69 | b[i] = v 70 | } 71 | joint := make([]byte, 0) 72 | joint = append(b, content...) 73 | return joint 74 | } 75 | 76 | //默认前十二位为命令名称 77 | func splitMessage(message []byte) (cmd string, content []byte) { 78 | cmdBytes := message[:prefixCMDLength] 79 | newCMDBytes := make([]byte, 0) 80 | for _, v := range cmdBytes { 81 | if v != byte(0) { 82 | newCMDBytes = append(newCMDBytes, v) 83 | } 84 | } 85 | cmd = string(newCMDBytes) 86 | content = message[prefixCMDLength:] 87 | return 88 | } 89 | 90 | //对消息详情进行摘要 91 | func getDigest(request Request) string { 92 | b, err := json.Marshal(request) 93 | if err != nil { 94 | log.Panic(err) 95 | } 96 | hash := sha256.Sum256(b) 97 | //进行十六进制字符串编码 98 | return hex.EncodeToString(hash[:]) 99 | } 100 | -------------------------------------------------------------------------------- /pbft/images/关闭两个节点.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/pbft/images/关闭两个节点.png -------------------------------------------------------------------------------- /pbft/images/启动.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/pbft/images/启动.png -------------------------------------------------------------------------------- /pbft/images/启动后.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/pbft/images/启动后.png -------------------------------------------------------------------------------- /pbft/images/掉了一个节点后.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/pbft/images/掉了一个节点后.png -------------------------------------------------------------------------------- /pbft/images/流程图.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/pbft/images/流程图.webp -------------------------------------------------------------------------------- /pbft/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | const nodeCount = 4 9 | 10 | //客户端的监听地址 11 | var clientAddr = "127.0.0.1:8888" 12 | 13 | //节点池,主要用来存储监听地址 14 | var nodeTable map[string]string 15 | 16 | func main() { 17 | //为四个节点生成公私钥 18 | genRsaKeys() 19 | nodeTable = map[string]string{ 20 | "N0": "127.0.0.1:8000", 21 | "N1": "127.0.0.1:8001", 22 | "N2": "127.0.0.1:8002", 23 | "N3": "127.0.0.1:8003", 24 | } 25 | if len(os.Args) != 2 { 26 | log.Panic("输入的参数有误!") 27 | } 28 | nodeID := os.Args[1] 29 | if nodeID == "client" { 30 | clientSendMessageAndListen() //启动客户端程序 31 | } else if addr, ok := nodeTable[nodeID]; ok { 32 | p := NewPBFT(nodeID, addr) 33 | go p.tcpListen() //启动节点 34 | } else { 35 | log.Fatal("无此节点编号!") 36 | } 37 | select {} 38 | } 39 | -------------------------------------------------------------------------------- /pbft/pbft.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "strconv" 10 | "sync" 11 | ) 12 | 13 | //本地消息池(模拟持久化层),只有确认提交成功后才会存入此池 14 | var localMessagePool = []Message{} 15 | 16 | type node struct { 17 | //节点ID 18 | nodeID string 19 | //节点监听地址 20 | addr string 21 | //RSA私钥 22 | rsaPrivKey []byte 23 | //RSA公钥 24 | rsaPubKey []byte 25 | } 26 | 27 | type pbft struct { 28 | //节点信息 29 | node node 30 | //每笔请求自增序号 31 | sequenceID int 32 | //锁 33 | lock sync.Mutex 34 | //临时消息池,消息摘要对应消息本体 35 | messagePool map[string]Request 36 | //存放收到的prepare数量(至少需要收到并确认2f个),根据摘要来对应 37 | prePareConfirmCount map[string]map[string]bool 38 | //存放收到的commit数量(至少需要收到并确认2f+1个),根据摘要来对应 39 | commitConfirmCount map[string]map[string]bool 40 | //该笔消息是否已进行Commit广播 41 | isCommitBordcast map[string]bool 42 | //该笔消息是否已对客户端进行Reply 43 | isReply map[string]bool 44 | } 45 | 46 | func NewPBFT(nodeID, addr string) *pbft { 47 | p := new(pbft) 48 | p.node.nodeID = nodeID 49 | p.node.addr = addr 50 | p.node.rsaPrivKey = p.getPivKey(nodeID) //从生成的私钥文件处读取 51 | p.node.rsaPubKey = p.getPubKey(nodeID) //从生成的私钥文件处读取 52 | p.sequenceID = 0 53 | p.messagePool = make(map[string]Request) 54 | p.prePareConfirmCount = make(map[string]map[string]bool) 55 | p.commitConfirmCount = make(map[string]map[string]bool) 56 | p.isCommitBordcast = make(map[string]bool) 57 | p.isReply = make(map[string]bool) 58 | return p 59 | } 60 | 61 | func (p *pbft) handleRequest(data []byte) { 62 | //切割消息,根据消息命令调用不同的功能 63 | cmd, content := splitMessage(data) 64 | switch command(cmd) { 65 | case cRequest: 66 | p.handleClientRequest(content) 67 | case cPrePrepare: 68 | p.handlePrePrepare(content) 69 | case cPrepare: 70 | p.handlePrepare(content) 71 | case cCommit: 72 | p.handleCommit(content) 73 | } 74 | } 75 | 76 | //处理客户端发来的请求 77 | func (p *pbft) handleClientRequest(content []byte) { 78 | fmt.Println("主节点已接收到客户端发来的request ...") 79 | //使用json解析出Request结构体 80 | r := new(Request) 81 | err := json.Unmarshal(content, r) 82 | if err != nil { 83 | log.Panic(err) 84 | } 85 | //添加信息序号 86 | p.sequenceIDAdd() 87 | //获取消息摘要 88 | digest := getDigest(*r) 89 | fmt.Println("已将request存入临时消息池") 90 | //存入临时消息池 91 | p.messagePool[digest] = *r 92 | //主节点对消息摘要进行签名 93 | digestByte, _ := hex.DecodeString(digest) 94 | signInfo := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) 95 | //拼接成PrePrepare,准备发往follower节点 96 | pp := PrePrepare{*r, digest, p.sequenceID, signInfo} 97 | b, err := json.Marshal(pp) 98 | if err != nil { 99 | log.Panic(err) 100 | } 101 | fmt.Println("正在向其他节点进行进行PrePrepare广播 ...") 102 | //进行PrePrepare广播 103 | p.broadcast(cPrePrepare, b) 104 | fmt.Println("PrePrepare广播完成") 105 | } 106 | 107 | //处理预准备消息 108 | func (p *pbft) handlePrePrepare(content []byte) { 109 | fmt.Println("本节点已接收到主节点发来的PrePrepare ...") 110 | // //使用json解析出PrePrepare结构体 111 | pp := new(PrePrepare) 112 | err := json.Unmarshal(content, pp) 113 | if err != nil { 114 | log.Panic(err) 115 | } 116 | //获取主节点的公钥,用于数字签名验证 117 | primaryNodePubKey := p.getPubKey("N0") 118 | digestByte, _ := hex.DecodeString(pp.Digest) 119 | if digest := getDigest(pp.RequestMessage); digest != pp.Digest { 120 | fmt.Println("信息摘要对不上,拒绝进行prepare广播") 121 | } else if p.sequenceID+1 != pp.SequenceID { 122 | fmt.Println("消息序号对不上,拒绝进行prepare广播") 123 | } else if !p.RsaVerySignWithSha256(digestByte, pp.Sign, primaryNodePubKey) { 124 | fmt.Println("主节点签名验证失败!,拒绝进行prepare广播") 125 | } else { 126 | //序号赋值 127 | p.sequenceID = pp.SequenceID 128 | //将信息存入临时消息池 129 | fmt.Println("已将消息存入临时节点池") 130 | p.messagePool[pp.Digest] = pp.RequestMessage 131 | //节点使用私钥对其签名 132 | sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) 133 | //拼接成Prepare 134 | pre := Prepare{pp.Digest, pp.SequenceID, p.node.nodeID, sign} 135 | bPre, err := json.Marshal(pre) 136 | if err != nil { 137 | log.Panic(err) 138 | } 139 | //进行准备阶段的广播 140 | fmt.Println("正在进行Prepare广播 ...") 141 | p.broadcast(cPrepare, bPre) 142 | fmt.Println("Prepare广播完成") 143 | } 144 | } 145 | 146 | //处理准备消息 147 | func (p *pbft) handlePrepare(content []byte) { 148 | //使用json解析出Prepare结构体 149 | pre := new(Prepare) 150 | err := json.Unmarshal(content, pre) 151 | if err != nil { 152 | log.Panic(err) 153 | } 154 | fmt.Printf("本节点已接收到%s节点发来的Prepare ... \n", pre.NodeID) 155 | //获取消息源节点的公钥,用于数字签名验证 156 | MessageNodePubKey := p.getPubKey(pre.NodeID) 157 | digestByte, _ := hex.DecodeString(pre.Digest) 158 | if _, ok := p.messagePool[pre.Digest]; !ok { 159 | fmt.Println("当前临时消息池无此摘要,拒绝执行commit广播") 160 | } else if p.sequenceID != pre.SequenceID { 161 | fmt.Println("消息序号对不上,拒绝执行commit广播") 162 | } else if !p.RsaVerySignWithSha256(digestByte, pre.Sign, MessageNodePubKey) { 163 | fmt.Println("节点签名验证失败!,拒绝执行commit广播") 164 | } else { 165 | p.setPrePareConfirmMap(pre.Digest, pre.NodeID, true) 166 | count := 0 167 | for range p.prePareConfirmCount[pre.Digest] { 168 | count++ 169 | } 170 | //因为主节点不会发送Prepare,所以不包含自己 171 | specifiedCount := 0 172 | if p.node.nodeID == "N0" { 173 | specifiedCount = nodeCount / 3 * 2 174 | } else { 175 | specifiedCount = (nodeCount / 3 * 2) - 1 176 | } 177 | //如果节点至少收到了2f个prepare的消息(包括自己),并且没有进行过commit广播,则进行commit广播 178 | p.lock.Lock() 179 | //获取消息源节点的公钥,用于数字签名验证 180 | if count >= specifiedCount && !p.isCommitBordcast[pre.Digest] { 181 | fmt.Println("本节点已收到至少2f个节点(包括本地节点)发来的Prepare信息 ...") 182 | //节点使用私钥对其签名 183 | sign := p.RsaSignWithSha256(digestByte, p.node.rsaPrivKey) 184 | c := Commit{pre.Digest, pre.SequenceID, p.node.nodeID, sign} 185 | bc, err := json.Marshal(c) 186 | if err != nil { 187 | log.Panic(err) 188 | } 189 | //进行提交信息的广播 190 | fmt.Println("正在进行commit广播") 191 | p.broadcast(cCommit, bc) 192 | p.isCommitBordcast[pre.Digest] = true 193 | fmt.Println("commit广播完成") 194 | } 195 | p.lock.Unlock() 196 | } 197 | } 198 | 199 | //处理提交确认消息 200 | func (p *pbft) handleCommit(content []byte) { 201 | //使用json解析出Commit结构体 202 | c := new(Commit) 203 | err := json.Unmarshal(content, c) 204 | if err != nil { 205 | log.Panic(err) 206 | } 207 | fmt.Printf("本节点已接收到%s节点发来的Commit ... \n", c.NodeID) 208 | //获取消息源节点的公钥,用于数字签名验证 209 | MessageNodePubKey := p.getPubKey(c.NodeID) 210 | digestByte, _ := hex.DecodeString(c.Digest) 211 | if _, ok := p.prePareConfirmCount[c.Digest]; !ok { 212 | fmt.Println("当前prepare池无此摘要,拒绝将信息持久化到本地消息池") 213 | } else if p.sequenceID != c.SequenceID { 214 | fmt.Println("消息序号对不上,拒绝将信息持久化到本地消息池") 215 | } else if !p.RsaVerySignWithSha256(digestByte, c.Sign, MessageNodePubKey) { 216 | fmt.Println("节点签名验证失败!,拒绝将信息持久化到本地消息池") 217 | } else { 218 | p.setCommitConfirmMap(c.Digest, c.NodeID, true) 219 | count := 0 220 | for range p.commitConfirmCount[c.Digest] { 221 | count++ 222 | } 223 | //如果节点至少收到了2f+1个commit消息(包括自己),并且节点没有回复过,并且已进行过commit广播,则提交信息至本地消息池,并reply成功标志至客户端! 224 | p.lock.Lock() 225 | if count >= nodeCount/3*2 && !p.isReply[c.Digest] && p.isCommitBordcast[c.Digest] { 226 | fmt.Println("本节点已收到至少2f + 1 个节点(包括本地节点)发来的Commit信息 ...") 227 | //将消息信息,提交到本地消息池中! 228 | localMessagePool = append(localMessagePool, p.messagePool[c.Digest].Message) 229 | info := p.node.nodeID + "节点已将msgid:" + strconv.Itoa(p.messagePool[c.Digest].ID) + "存入本地消息池中,消息内容为:" + p.messagePool[c.Digest].Content 230 | fmt.Println(info) 231 | fmt.Println("正在reply客户端 ...") 232 | tcpDial([]byte(info), p.messagePool[c.Digest].ClientAddr) 233 | p.isReply[c.Digest] = true 234 | fmt.Println("reply完毕") 235 | } 236 | p.lock.Unlock() 237 | } 238 | } 239 | 240 | //序号累加 241 | func (p *pbft) sequenceIDAdd() { 242 | p.lock.Lock() 243 | p.sequenceID++ 244 | p.lock.Unlock() 245 | } 246 | 247 | //向除自己外的其他节点进行广播 248 | func (p *pbft) broadcast(cmd command, content []byte) { 249 | for i := range nodeTable { 250 | if i == p.node.nodeID { 251 | continue 252 | } 253 | message := jointMessage(cmd, content) 254 | go tcpDial(message, nodeTable[i]) 255 | } 256 | } 257 | 258 | //为多重映射开辟赋值 259 | func (p *pbft) setPrePareConfirmMap(val, val2 string, b bool) { 260 | if _, ok := p.prePareConfirmCount[val]; !ok { 261 | p.prePareConfirmCount[val] = make(map[string]bool) 262 | } 263 | p.prePareConfirmCount[val][val2] = b 264 | } 265 | 266 | //为多重映射开辟赋值 267 | func (p *pbft) setCommitConfirmMap(val, val2 string, b bool) { 268 | if _, ok := p.commitConfirmCount[val]; !ok { 269 | p.commitConfirmCount[val] = make(map[string]bool) 270 | } 271 | p.commitConfirmCount[val][val2] = b 272 | } 273 | 274 | //传入节点编号, 获取对应的公钥 275 | func (p *pbft) getPubKey(nodeID string) []byte { 276 | key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PUB") 277 | if err != nil { 278 | log.Panic(err) 279 | } 280 | return key 281 | } 282 | 283 | //传入节点编号, 获取对应的私钥 284 | func (p *pbft) getPivKey(nodeID string) []byte { 285 | key, err := ioutil.ReadFile("Keys/" + nodeID + "/" + nodeID + "_RSA_PIV") 286 | if err != nil { 287 | log.Panic(err) 288 | } 289 | return key 290 | } 291 | -------------------------------------------------------------------------------- /pbft/rsa.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/pem" 10 | "errors" 11 | "fmt" 12 | "log" 13 | "os" 14 | "strconv" 15 | ) 16 | 17 | //如果当前目录下不存在目录Keys,则创建目录,并为各个节点生成rsa公私钥 18 | func genRsaKeys() { 19 | if !isExist("./Keys") { 20 | fmt.Println("检测到还未生成公私钥目录,正在生成公私钥 ...") 21 | err := os.Mkdir("Keys", 0644) 22 | if err != nil { 23 | log.Panic() 24 | } 25 | for i := 0; i <= 4; i++ { 26 | if !isExist("./Keys/N" + strconv.Itoa(i)) { 27 | err := os.Mkdir("./Keys/N"+strconv.Itoa(i), 0644) 28 | if err != nil { 29 | log.Panic() 30 | } 31 | } 32 | priv, pub := getKeyPair() 33 | privFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PIV" 34 | file, err := os.OpenFile(privFileName, os.O_RDWR|os.O_CREATE, 0644) 35 | if err != nil { 36 | log.Panic(err) 37 | } 38 | defer file.Close() 39 | file.Write(priv) 40 | 41 | pubFileName := "Keys/N" + strconv.Itoa(i) + "/N" + strconv.Itoa(i) + "_RSA_PUB" 42 | file2, err := os.OpenFile(pubFileName, os.O_RDWR|os.O_CREATE, 0644) 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | defer file2.Close() 47 | file2.Write(pub) 48 | } 49 | fmt.Println("已为节点们生成RSA公私钥") 50 | } 51 | } 52 | 53 | //生成rsa公私钥 54 | func getKeyPair() (prvkey, pubkey []byte) { 55 | // 生成私钥文件 56 | privateKey, err := rsa.GenerateKey(rand.Reader, 1024) 57 | if err != nil { 58 | panic(err) 59 | } 60 | derStream := x509.MarshalPKCS1PrivateKey(privateKey) 61 | block := &pem.Block{ 62 | Type: "RSA PRIVATE KEY", 63 | Bytes: derStream, 64 | } 65 | prvkey = pem.EncodeToMemory(block) 66 | publicKey := &privateKey.PublicKey 67 | derPkix, err := x509.MarshalPKIXPublicKey(publicKey) 68 | if err != nil { 69 | panic(err) 70 | } 71 | block = &pem.Block{ 72 | Type: "PUBLIC KEY", 73 | Bytes: derPkix, 74 | } 75 | pubkey = pem.EncodeToMemory(block) 76 | return 77 | } 78 | 79 | //判断文件或文件夹是否存在 80 | func isExist(path string) bool { 81 | _, err := os.Stat(path) 82 | if err != nil { 83 | if os.IsExist(err) { 84 | return true 85 | } 86 | if os.IsNotExist(err) { 87 | return false 88 | } 89 | fmt.Println(err) 90 | return false 91 | } 92 | return true 93 | } 94 | 95 | //数字签名 96 | func (p *pbft) RsaSignWithSha256(data []byte, keyBytes []byte) []byte { 97 | h := sha256.New() 98 | h.Write(data) 99 | hashed := h.Sum(nil) 100 | block, _ := pem.Decode(keyBytes) 101 | if block == nil { 102 | panic(errors.New("private key error")) 103 | } 104 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 105 | if err != nil { 106 | fmt.Println("ParsePKCS8PrivateKey err", err) 107 | panic(err) 108 | } 109 | 110 | signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed) 111 | if err != nil { 112 | fmt.Printf("Error from signing: %s\n", err) 113 | panic(err) 114 | } 115 | 116 | return signature 117 | } 118 | 119 | //签名验证 120 | func (p *pbft) RsaVerySignWithSha256(data, signData, keyBytes []byte) bool { 121 | block, _ := pem.Decode(keyBytes) 122 | if block == nil { 123 | panic(errors.New("public key error")) 124 | } 125 | pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) 126 | if err != nil { 127 | panic(err) 128 | } 129 | 130 | hashed := sha256.Sum256(data) 131 | err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], signData) 132 | if err != nil { 133 | panic(err) 134 | } 135 | return true 136 | } 137 | -------------------------------------------------------------------------------- /pbft/tcp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | ) 9 | 10 | //客户端使用的tcp监听 11 | func clientTcpListen() { 12 | listen, err := net.Listen("tcp", clientAddr) 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | defer listen.Close() 17 | 18 | for { 19 | conn, err := listen.Accept() 20 | if err != nil { 21 | log.Panic(err) 22 | } 23 | b, err := ioutil.ReadAll(conn) 24 | if err != nil { 25 | log.Panic(err) 26 | } 27 | fmt.Println(string(b)) 28 | } 29 | 30 | } 31 | 32 | //节点使用的tcp监听 33 | func (p *pbft) tcpListen() { 34 | listen, err := net.Listen("tcp", p.node.addr) 35 | if err != nil { 36 | log.Panic(err) 37 | } 38 | fmt.Printf("节点开启监听,地址:%s\n", p.node.addr) 39 | defer listen.Close() 40 | 41 | for { 42 | conn, err := listen.Accept() 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | b, err := ioutil.ReadAll(conn) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | p.handleRequest(b) 51 | } 52 | 53 | } 54 | 55 | //使用tcp发送消息 56 | func tcpDial(context []byte, addr string) { 57 | conn, err := net.Dial("tcp", addr) 58 | if err != nil { 59 | log.Println("connect error", err) 60 | return 61 | } 62 | 63 | _, err = conn.Write(context) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | conn.Close() 68 | } 69 | -------------------------------------------------------------------------------- /pos/README.md: -------------------------------------------------------------------------------- 1 | 2 | >权益证明机制最开始是由点点币提出并应用(出块概率=代币数量 * 币龄) 简单来说谁的币多,谁就有更大的出块概率。但是深挖下去,这个出块概率谁来计算?碰到无成本利益关系问题怎么办?这个共识算法初看很简单,实际有很多问题需要解决,且看以太坊什么时候能完全转换到POS机制吧 3 | 4 |
5 | 6 | 区块结构 7 | ```go 8 | type block struct { 9 | //上一个块的hash 10 | prehash string 11 | //本块hash 12 | hash string 13 | //时间戳 14 | timestamp string 15 | //区块内容 16 | data string 17 | //区块高度 18 | height int 19 | //挖出本块的地址 20 | address string 21 | } 22 | ``` 23 | 声明两个节点池\ 24 | mineNodesPool 用来存放指定的挖矿节点\ 25 | probabilityNodesPool 用于存入挖矿节点的代币数量*币龄获得的概率 26 | ```go 27 | //用于存储区块链 28 | var blockchain []block 29 | //代表挖矿节点 30 | type node struct{ 31 | //代币数量 32 | tokens int 33 | //质押时间 34 | days int 35 | //节点地址 36 | address string 37 | } 38 | //挖矿节点 39 | var mineNodesPool []node 40 | //概率节点池 41 | var probabilityNodesPool []node 42 | ``` 43 | 初始化节点池: 44 | ```go 45 | func init () { 46 | //手动添加两个节点 47 | mineNodesPool = append(mineNodesPool,node{1000,1,"AAAAAAAAAA"}) 48 | mineNodesPool = append(mineNodesPool,node{100,3,"BBBBBBBBBB"}) 49 | //初始化随机节点池(挖矿概率与代币数量和币龄有关) 50 | for _,v:=range mineNodesPool{ 51 | for i:=0;i<=v.tokens * v.days; i ++ { 52 | randNodesPool = append(randNodesPool,v) 53 | } 54 | } 55 | } 56 | ``` 57 | 每次挖矿都会从概率节点池中随机选出获得出块权的节点地址 58 | ```go 59 | //随机得出挖矿地址(挖矿概率跟代币数量与币龄有关) 60 | func getMineNodeAddress() string{ 61 | bInt:=big.NewInt(int64(len(randNodesPool))) 62 | //得出一个随机数,最大不超过随机节点池的大小 63 | rInt,err:=rand.Int(rand.Reader,bInt) 64 | if err != nil { 65 | log.Panic(err) 66 | } 67 | return randNodesPool[int(rInt.Int64())].address 68 | } 69 | ``` 70 | 71 | 72 | ```go 73 | func main() { 74 | //创建创世区块 75 | genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000","",time.Now().Format("2006-01-02 15:04:05"),"我是创世区块",1,"0000000000"} 76 | genesisBlock.getHash() 77 | //把创世区块添加进区块链 78 | blockchain = append(blockchain,genesisBlock) 79 | fmt.Println(blockchain[0]) 80 | i:=0 81 | for { 82 | time.Sleep(time.Second) 83 | newBlock:=generateNewBlock(blockchain[i],"我是区块内容","00000") 84 | blockchain = append(blockchain,newBlock) 85 | fmt.Println(blockchain[i + 1]) 86 | i++ 87 | } 88 | } 89 | ``` 90 | 91 | 运行结果: 92 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211154915783.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70) 93 | -------------------------------------------------------------------------------- /pos/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | type block struct { 15 | //上一个块的hash 16 | prehash string 17 | //本块hash 18 | hash string 19 | //时间戳 20 | timestamp string 21 | //区块内容 22 | data string 23 | //区块高度 24 | height int 25 | //挖出本块的地址 26 | address string 27 | } 28 | 29 | //用于存储区块链 30 | var blockchain []block 31 | 32 | //代表挖矿节点 33 | type node struct { 34 | //代币数量 35 | tokens int 36 | //质押时间 37 | days int 38 | //节点地址 39 | address string 40 | } 41 | 42 | //挖矿节点 43 | var mineNodesPool []node 44 | 45 | //概率节点池 46 | var probabilityNodesPool []node 47 | 48 | //初始化 49 | func init() { 50 | //手动添加两个节点 51 | mineNodesPool = append(mineNodesPool, node{1000, 1, "AAAAAAAAAA"}) 52 | mineNodesPool = append(mineNodesPool, node{100, 3, "BBBBBBBBBB"}) 53 | //初始化随机节点池(挖矿概率与代币数量和币龄有关) 54 | for _, v := range mineNodesPool { 55 | for i := 0; i <= v.tokens*v.days; i++ { 56 | probabilityNodesPool = append(probabilityNodesPool, v) 57 | } 58 | } 59 | } 60 | 61 | //生成新的区块 62 | func generateNewBlock(oldBlock block, data string, address string) block { 63 | newBlock := block{} 64 | newBlock.prehash = oldBlock.hash 65 | newBlock.data = data 66 | newBlock.timestamp = time.Now().Format("2006-01-02 15:04:05") 67 | newBlock.height = oldBlock.height + 1 68 | newBlock.address = getMineNodeAddress() 69 | newBlock.getHash() 70 | return newBlock 71 | } 72 | 73 | //对自身进行散列 74 | func (b *block) getHash() { 75 | sumString := b.prehash + b.timestamp + b.data + b.address + strconv.Itoa(b.height) 76 | hash := sha256.Sum256([]byte(sumString)) 77 | b.hash = hex.EncodeToString(hash[:]) 78 | } 79 | 80 | //随机得出挖矿地址(挖矿概率跟代币数量与币龄有关) 81 | func getMineNodeAddress() string { 82 | bInt := big.NewInt(int64(len(probabilityNodesPool))) 83 | //得出一个随机数,最大不超过随机节点池的大小 84 | rInt, err := rand.Int(rand.Reader, bInt) 85 | if err != nil { 86 | log.Panic(err) 87 | } 88 | return probabilityNodesPool[int(rInt.Int64())].address 89 | } 90 | 91 | func main() { 92 | //创建创世区块 93 | genesisBlock := block{"0000000000000000000000000000000000000000000000000000000000000000", "", time.Now().Format("2006-01-02 15:04:05"), "我是创世区块", 1, "0000000000"} 94 | genesisBlock.getHash() 95 | //把创世区块添加进区块链 96 | blockchain = append(blockchain, genesisBlock) 97 | fmt.Println(blockchain[0]) 98 | i := 0 99 | for { 100 | time.Sleep(time.Second) 101 | newBlock := generateNewBlock(blockchain[i], "我是区块内容", "00000") 102 | blockchain = append(blockchain, newBlock) 103 | fmt.Println(blockchain[i+1]) 104 | i++ 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pow/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | >工作量证明机制的核心在于不断hash区块自身,将hash值与根据难度值计算出的一串大数对比,如果自身hash小于大数则说明挖矿成功,否则变化自身随机数重新计算。并且程序会随着出块间隔时间动态调节难度值(比如比特币) 6 | 7 |
8 | 9 | 区块结构 10 | ```go 11 | type block struct { 12 | //上一个区块的Hash 13 | Lasthash string 14 | //本区块Hash 15 | Hash string 16 | //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易) 17 | Data string 18 | //时间戳 19 | Timestamp string 20 | //区块高度 21 | Height int 22 | //难度值 23 | DiffNum uint 24 | //随机数 25 | Nonce int64 26 | } 27 | ``` 28 | 挖矿函数:\ 29 | 使用math/big包,根据全局变量的难度值diffNum计算出用于实际比较的一串大数newBigint ,并同时将区块hash转换为大数hashInt 两个大数进行数值比较,如果hashInt小于newBigint 则代表挖矿成功 30 | 31 | ```go 32 | //区块挖矿(通过自身递增nonce值计算hash) 33 | func mine(data string) block { 34 | if len(blockchain) < 1 { 35 | log.Panic("还未生成创世区块!") 36 | } 37 | lastBlock := blockchain[len(blockchain)-1] 38 | //制造一个新的区块 39 | newBlock := new(block) 40 | newBlock.Lasthash = lastBlock.Hash 41 | newBlock.Timestamp = time.Now().String() 42 | newBlock.Height = lastBlock.Height + 1 43 | newBlock.DiffNum = diffNum 44 | newBlock.Data = data 45 | var nonce int64 = 0 46 | //根据挖矿难度值计算的一个大数 47 | newBigint := big.NewInt(1) 48 | newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum 49 | for { 50 | newBlock.Nonce = nonce 51 | newBlock.getHash() 52 | hashInt := big.Int{} 53 | hashBytes, _ := hex.DecodeString(newBlock.Hash) 54 | hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字 55 | //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功 56 | if hashInt.Cmp(newBigint) == -1 { 57 | break 58 | } else { 59 | nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数 60 | } 61 | } 62 | return *newBlock 63 | } 64 | ``` 65 | 66 | ```go 67 | func main() { 68 | //制造一个创世区块 69 | genesisBlock := new(block) 70 | genesisBlock.Timestamp = time.Now().String() 71 | genesisBlock.Data = "我是创世区块!" 72 | genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000" 73 | genesisBlock.Height = 1 74 | genesisBlock.Nonce = 0 75 | genesisBlock.DiffNum = 0 76 | genesisBlock.getHash() 77 | fmt.Println(*genesisBlock) 78 | //将创世区块添加进区块链 79 | blockchain = append(blockchain, *genesisBlock) 80 | for i := 0; i < 10; i++ { 81 | newBlock := mine("天气不错"+strconv.Itoa(i)) 82 | blockchain = append(blockchain, newBlock) 83 | fmt.Println(newBlock) 84 | } 85 | ``` 86 | 87 | 运行结果: 88 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211145732513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1OTExMTg0,size_16,color_FFFFFF,t_70) 89 | 90 | -------------------------------------------------------------------------------- /pow/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | //用于存放区块,以便连接成区块链 15 | var blockchain []block 16 | 17 | //挖矿难度值 18 | var diffNum uint = 17 19 | 20 | type block struct { 21 | //上一个区块的Hash 22 | Lasthash string 23 | //本区块Hash 24 | Hash string 25 | //区块存储的数据(比如比特币UTXO模型 则此处可用于存储交易) 26 | Data string 27 | //时间戳 28 | Timestamp string 29 | //区块高度 30 | Height int 31 | //难度值 32 | DiffNum uint 33 | //随机数 34 | Nonce int64 35 | } 36 | 37 | //区块挖矿(通过自身递增nonce值计算hash) 38 | func mine(data string) block { 39 | if len(blockchain) < 1 { 40 | log.Panic("还未生成创世区块!") 41 | } 42 | lastBlock := blockchain[len(blockchain)-1] 43 | //制造一个新的区块 44 | newBlock := new(block) 45 | newBlock.Lasthash = lastBlock.Hash 46 | newBlock.Timestamp = time.Now().String() 47 | newBlock.Height = lastBlock.Height + 1 48 | newBlock.DiffNum = diffNum 49 | newBlock.Data = data 50 | var nonce int64 = 0 51 | //根据挖矿难度值计算的一个大数 52 | newBigint := big.NewInt(1) 53 | newBigint.Lsh(newBigint, 256-diffNum) //相当于左移 1<<256-diffNum 54 | for { 55 | newBlock.Nonce = nonce 56 | newBlock.getHash() 57 | hashInt := big.Int{} 58 | hashBytes, _ := hex.DecodeString(newBlock.Hash) 59 | hashInt.SetBytes(hashBytes) //把本区块hash值转换为一串数字 60 | //如果hash小于挖矿难度值计算的一个大数,则代表挖矿成功 61 | if hashInt.Cmp(newBigint) == -1 { 62 | break 63 | } else { 64 | nonce++ //不满足条件,则不断递增随机数,直到本区块的散列值小于指定的大数 65 | } 66 | } 67 | return *newBlock 68 | } 69 | 70 | func (b *block) serialize() []byte { 71 | bytes, err := json.Marshal(b) 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | return bytes 76 | } 77 | 78 | func (b *block) getHash() { 79 | result := sha256.Sum256(b.serialize()) 80 | b.Hash = hex.EncodeToString(result[:]) 81 | } 82 | 83 | func main() { 84 | //制造一个创世区块 85 | genesisBlock := new(block) 86 | genesisBlock.Timestamp = time.Now().String() 87 | genesisBlock.Data = "我是创世区块!" 88 | genesisBlock.Lasthash = "0000000000000000000000000000000000000000000000000000000000000000" 89 | genesisBlock.Height = 1 90 | genesisBlock.Nonce = 0 91 | genesisBlock.DiffNum = 0 92 | genesisBlock.getHash() 93 | fmt.Println(*genesisBlock) 94 | //将创世区块添加进区块链 95 | blockchain = append(blockchain, *genesisBlock) 96 | for i := 0; i < 10; i++ { 97 | newBlock := mine("天气不错" + strconv.Itoa(i)) 98 | blockchain = append(blockchain, newBlock) 99 | fmt.Println(newBlock) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /raft/README.md: -------------------------------------------------------------------------------- 1 | >**   邮箱:mikesen1994@gmail.com            vx:965952482** 2 | 3 |
4 | 5 | 6 | 本demo为raft的代码实现,如果想了解raft的详细信息请自行浏览参考资料
7 | 本demo展示了raft的部分功能,写的并不严谨,仅作为对raft的了解用途 8 |
9 | 10 | ## 实现功能: 11 | 12 | - 节点状态分为Leader(领导者)、Follower(追随者)、Candidate(候选人) 13 | - 节点间随机成为candidate状态并选举出Leader,且同时仅存在一个Leader 14 | - Leader节点定时发送心跳检测至其他Follower节点 15 | - Follower节点们超过一定时间未收到心跳检测,则Follower节点们重新开启选举 16 | - 客户端通过http发送消息到节点A,如果A不是Leader则转发至Leader节点 17 | - Leader收到客户端的消息后向Follower节点进行广播 18 | - Follower节点收到消息,反馈给Leader,等待Leader确认 19 | - Leader收到全网超过二分之一的反馈后,本地进行打印,然后将确认收到反馈的信息提交到Follower节点 20 | - Follower节点收到确认提交信息后,打印消息 21 | 22 |
23 | 24 | ## 运行步骤: 25 |
26 | 27 | ##### 1.下载/编译 28 | ```shell 29 | git clone https://github.com/corgi-kx/blockchain_consensus_algorithm.git 30 | ``` 31 | ```shell 32 | cd blockchain_consensus_algorithm/raft 33 | ``` 34 | ```go 35 | go build -o raft.exe 36 | ``` 37 | 38 | ##### 2.开启三个端口,并分别执行raft.exe A 、raft.exe B 、 raft.exe C,代表开启三个节点(初始状态为追随者) 39 | ![在这里插入图片描述](images/开启端口.png) 40 | 41 | ##### 3.三个节点会随机选举出领导者(其中A节点默认监听来自http的访问),成功的节点会发送心跳检测到其他两个节点 42 | ![在这里插入图片描述](images/选举成功.png) 43 | ##### 4.此时打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,比如: 44 | `http://localhost:8080/req?message=噢,我的上帝呀` 45 | ![在这里插入图片描述](images/打印消息.png) 46 | 可以看到三个节点同时打印了消息,本段数据同步步骤可以用下图进行理解(不过缺少了4.1步骤) 47 | ![在这里插入图片描述](images/消息同步.png) 48 | ##### 5.如果领导者节点宕机了怎么办呢,我们尝试关闭领导者节点B 49 | ![在这里插入图片描述](images/领导者节点宕机.png) 50 | 可以发现关闭领导者B后,节点间有个超时机制,如果超过一定时间没有收到心跳检测,则会自动开始重新进行选举,此时A当选了新的领导者 51 | 52 | ##### 6.再次打开浏览器用http访问本地节点8080端口,带上节点需要同步打印的消息,看看还能同步打印吗 53 | `http://localhost:8080/req?message=天气不错` 54 | ![在这里插入图片描述](images/残缺打印.png) 55 | 结果发现可以打印的,因为新的领导者A、追随者C并没有宕机,A收到了C的回馈(2>3/2)超过了全网一半的节点,所以是可以进行打印数据的 56 | 57 | ##### 7.重新启动节点B,B自动变为追随者状态,并接收来自新的领导者A的心跳检测 58 | ![在这里插入图片描述](images/重启B.png) 59 |
60 | 61 | >参考资料: 62 | > - http://thesecretlivesofdata.com/raft/ 63 | > - https://www.cnblogs.com/mindwind/p/5231986.html 64 | > - https://blog.csdn.net/s15738841819/article/details/84286276 65 | -------------------------------------------------------------------------------- /raft/http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "net/http" 9 | "net/rpc" 10 | ) 11 | 12 | //等待节点访问 13 | func (rf *Raft) getRequest(writer http.ResponseWriter, request *http.Request) { 14 | request.ParseForm() 15 | //http://localhost:8080/req?message=ohmygod 16 | if len(request.Form["message"]) > 0 && rf.currentLeader != "-1" { 17 | message := request.Form["message"][0] 18 | m := new(Message) 19 | m.MsgID = getRandom() 20 | m.Msg = message 21 | //接收到消息后,直接转发到领导者 22 | fmt.Println("http监听到了消息,准备发送给领导者,消息id:", m.MsgID) 23 | port := nodeTable[rf.currentLeader] 24 | rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port) 25 | if err != nil { 26 | log.Panic(err) 27 | } 28 | b := false 29 | err = rp.Call("Raft.LeaderReceiveMessage", m, &b) 30 | if err != nil { 31 | log.Panic(err) 32 | } 33 | fmt.Println("消息是否已发送到领导者:", b) 34 | writer.Write([]byte("ok!!!")) 35 | } 36 | } 37 | 38 | func (rf *Raft) httpListen() { 39 | //创建getRequest()回调方法 40 | http.HandleFunc("/req", rf.getRequest) 41 | fmt.Println("监听8080") 42 | if err := http.ListenAndServe(":8080", nil); err != nil { 43 | fmt.Println(err) 44 | return 45 | } 46 | } 47 | 48 | //返回一个十位数的随机数,作为消息idgit 49 | func getRandom() int { 50 | x := big.NewInt(10000000000) 51 | for { 52 | result, err := rand.Int(rand.Reader, x) 53 | if err != nil { 54 | log.Panic(err) 55 | } 56 | if result.Int64() > 1000000000 { 57 | return int(result.Int64()) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /raft/images/开启端口.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/开启端口.png -------------------------------------------------------------------------------- /raft/images/打印消息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/打印消息.png -------------------------------------------------------------------------------- /raft/images/残缺打印.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/残缺打印.png -------------------------------------------------------------------------------- /raft/images/消息同步.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/消息同步.png -------------------------------------------------------------------------------- /raft/images/选举成功.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/选举成功.png -------------------------------------------------------------------------------- /raft/images/重启B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/重启B.png -------------------------------------------------------------------------------- /raft/images/领导者节点宕机.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corgi-kx/blockchain_consensus_algorithm/7a66af4f2b702a2f4de5efbd4e6ff73cf11725a3/raft/images/领导者节点宕机.png -------------------------------------------------------------------------------- /raft/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | ) 9 | 10 | //定义节点数量 11 | var raftCount = 3 12 | 13 | //节点池 14 | var nodeTable map[string]string 15 | 16 | //选举超时时间(单位:秒) 17 | var timeout = 3 18 | 19 | //心跳检测超时时间 20 | var heartBeatTimeout = 7 21 | 22 | //心跳检测频率(单位:秒) 23 | var heartBeatTimes = 3 24 | 25 | //用于存储消息 26 | var MessageStore = make(map[int]string) 27 | 28 | func main() { 29 | //定义三个节点 节点编号 - 监听端口号 30 | nodeTable = map[string]string{ 31 | "A": ":9000", 32 | "B": ":9001", 33 | "C": ":9002", 34 | } 35 | //运行程序时候 指定节点编号 36 | if len(os.Args) < 1 { 37 | log.Fatal("程序参数不正确") 38 | } 39 | 40 | id := os.Args[1] 41 | //传入节点编号,端口号,创建raft实例 42 | raft := NewRaft(id, nodeTable[id]) 43 | //启用RPC,注册raft 44 | go rpcRegister(raft) 45 | //开启心跳检测 46 | go raft.heartbeat() 47 | //开启一个Http监听 48 | if id == "A" { 49 | go raft.httpListen() 50 | } 51 | 52 | Circle: 53 | //开启选举 54 | go func() { 55 | for { 56 | //成为候选人节点 57 | if raft.becomeCandidate() { 58 | //成为后选人节点后 向其他节点要选票来进行选举 59 | if raft.election() { 60 | break 61 | } else { 62 | continue 63 | } 64 | } else { 65 | break 66 | } 67 | } 68 | }() 69 | 70 | //进行心跳检测 71 | for { 72 | //0.5秒检测一次 73 | time.Sleep(time.Millisecond * 5000) 74 | if raft.lastHeartBeartTime != 0 && (millisecond()-raft.lastHeartBeartTime) > int64(raft.timeout*1000) { 75 | fmt.Printf("心跳检测超时,已超过%d秒\n", raft.timeout) 76 | fmt.Println("即将重新开启选举") 77 | raft.reDefault() 78 | raft.setCurrentLeader("-1") 79 | raft.lastHeartBeartTime = 0 80 | goto Circle 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /raft/raft.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | //声明raft节点类型 11 | type Raft struct { 12 | node *NodeInfo 13 | //本节点获得的投票数 14 | vote int 15 | //线程锁 16 | lock sync.Mutex 17 | //节点编号 18 | me string 19 | //当前任期 20 | currentTerm int 21 | //为哪个节点投票 22 | votedFor string 23 | //当前节点状态 24 | //0 follower 1 candidate 2 leader 25 | state int 26 | //发送最后一条消息的时间 27 | lastMessageTime int64 28 | //发送最后一条消息的时间 29 | lastHeartBeartTime int64 30 | //当前节点的领导 31 | currentLeader string 32 | //心跳超时时间(单位:秒) 33 | timeout int 34 | //接收投票成功通道 35 | voteCh chan bool 36 | //心跳信号 37 | heartBeat chan bool 38 | } 39 | 40 | type NodeInfo struct { 41 | ID string 42 | Port string 43 | } 44 | 45 | type Message struct { 46 | Msg string 47 | MsgID int 48 | } 49 | 50 | func NewRaft(id, port string) *Raft { 51 | node := new(NodeInfo) 52 | node.ID = id 53 | node.Port = port 54 | 55 | rf := new(Raft) 56 | //节点信息 57 | rf.node = node 58 | //当前节点获得票数 59 | rf.setVote(0) 60 | //编号 61 | rf.me = id 62 | //给0 1 2三个节点投票,给谁都不投 63 | rf.setVoteFor("-1") 64 | //0 follower 65 | rf.setStatus(0) 66 | //最后一次心跳检测时间 67 | rf.lastHeartBeartTime = 0 68 | rf.timeout = heartBeatTimeout 69 | //最初没有领导 70 | rf.setCurrentLeader("-1") 71 | //设置任期 72 | rf.setTerm(0) 73 | //投票通道 74 | rf.voteCh = make(chan bool) 75 | //心跳通道 76 | rf.heartBeat = make(chan bool) 77 | return rf 78 | } 79 | 80 | //修改节点为候选人状态 81 | func (rf *Raft) becomeCandidate() bool { 82 | r := randRange(1500, 5000) 83 | //休眠随机时间后,再开始成为候选人 84 | time.Sleep(time.Duration(r) * time.Millisecond) 85 | //如果发现本节点已经投过票,或者已经存在领导者,则不用变身候选人状态 86 | if rf.state == 0 && rf.currentLeader == "-1" && rf.votedFor == "-1" { 87 | //将节点状态变为1 88 | rf.setStatus(1) 89 | //设置为哪个节点投票 90 | rf.setVoteFor(rf.me) 91 | //节点任期加1 92 | rf.setTerm(rf.currentTerm + 1) 93 | //当前没有领导 94 | rf.setCurrentLeader("-1") 95 | //为自己投票 96 | rf.voteAdd() 97 | fmt.Println("本节点已变更为候选人状态") 98 | fmt.Printf("当前得票数:%d\n", rf.vote) 99 | //开启选举通道 100 | return true 101 | } else { 102 | return false 103 | } 104 | } 105 | 106 | //进行选举 107 | func (rf *Raft) election() bool { 108 | fmt.Println("开始进行领导者选举,向其他节点进行广播") 109 | go rf.broadcast("Raft.Vote", rf.node, func(ok bool) { 110 | rf.voteCh <- ok 111 | }) 112 | for { 113 | select { 114 | case <-time.After(time.Second * time.Duration(timeout)): 115 | fmt.Println("领导者选举超时,节点变更为追随者状态\n") 116 | rf.reDefault() 117 | return false 118 | case ok := <-rf.voteCh: 119 | if ok { 120 | rf.voteAdd() 121 | fmt.Printf("获得来自其他节点的投票,当前得票数:%d\n", rf.vote) 122 | } 123 | if rf.vote > raftCount/2 && rf.currentLeader == "-1" { 124 | fmt.Println("获得超过网络节点二分之一的得票数,本节点被选举成为了leader") 125 | //节点状态变为2,代表leader 126 | rf.setStatus(2) 127 | //当前领导者为自己 128 | rf.setCurrentLeader(rf.me) 129 | fmt.Println("向其他节点进行广播...") 130 | go rf.broadcast("Raft.ConfirmationLeader", rf.node, func(ok bool) { 131 | fmt.Println(ok) 132 | }) 133 | //开启心跳检测通道 134 | rf.heartBeat <- true 135 | return true 136 | } 137 | } 138 | } 139 | } 140 | 141 | //心跳检测方法 142 | func (rf *Raft) heartbeat() { 143 | //如果收到通道开启的信息,将会向其他节点进行固定频率的心跳检测 144 | if <-rf.heartBeat { 145 | for { 146 | fmt.Println("本节点开始发送心跳检测...") 147 | rf.broadcast("Raft.HeartbeatRe", rf.node, func(ok bool) { 148 | fmt.Println("收到回复:", ok) 149 | }) 150 | rf.lastHeartBeartTime = millisecond() 151 | time.Sleep(time.Second * time.Duration(heartBeatTimes)) 152 | } 153 | } 154 | } 155 | 156 | //产生随机值 157 | func randRange(min, max int64) int64 { 158 | //用于心跳信号的时间 159 | rand.Seed(time.Now().UnixNano()) 160 | return rand.Int63n(max-min) + min 161 | } 162 | 163 | //获取当前时间的毫秒数 164 | func millisecond() int64 { 165 | return time.Now().UnixNano() / int64(time.Millisecond) 166 | } 167 | 168 | //设置任期 169 | func (rf *Raft) setTerm(term int) { 170 | rf.lock.Lock() 171 | rf.currentTerm = term 172 | rf.lock.Unlock() 173 | } 174 | 175 | //设置为谁投票 176 | func (rf *Raft) setVoteFor(id string) { 177 | rf.lock.Lock() 178 | rf.votedFor = id 179 | rf.lock.Unlock() 180 | } 181 | 182 | //设置当前领导者 183 | func (rf *Raft) setCurrentLeader(leader string) { 184 | rf.lock.Lock() 185 | rf.currentLeader = leader 186 | rf.lock.Unlock() 187 | } 188 | 189 | //设置当前领导者 190 | func (rf *Raft) setStatus(state int) { 191 | rf.lock.Lock() 192 | rf.state = state 193 | rf.lock.Unlock() 194 | } 195 | 196 | //投票累加 197 | func (rf *Raft) voteAdd() { 198 | rf.lock.Lock() 199 | rf.vote++ 200 | rf.lock.Unlock() 201 | } 202 | 203 | //设置投票数量 204 | func (rf *Raft) setVote(num int) { 205 | rf.lock.Lock() 206 | rf.vote = num 207 | rf.lock.Unlock() 208 | } 209 | 210 | //恢复默认设置 211 | func (rf *Raft) reDefault() { 212 | rf.setVote(0) 213 | //rf.currentLeader = "-1" 214 | rf.setVoteFor("-1") 215 | rf.setStatus(0) 216 | } 217 | -------------------------------------------------------------------------------- /raft/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "net/rpc" 8 | "time" 9 | ) 10 | 11 | //rpc服务注册 12 | func rpcRegister(raft *Raft) { 13 | //注册一个服务器 14 | err := rpc.Register(raft) 15 | if err != nil { 16 | log.Panic(err) 17 | } 18 | port := raft.node.Port 19 | //把服务绑定到http协议上 20 | rpc.HandleHTTP() 21 | //监听端口 22 | err = http.ListenAndServe(port, nil) 23 | if err != nil { 24 | fmt.Println("注册rpc服务失败", err) 25 | } 26 | } 27 | 28 | func (rf *Raft) broadcast(method string, args interface{}, fun func(ok bool)) { 29 | //设置不要自己给自己广播 30 | for nodeID, port := range nodeTable { 31 | if nodeID == rf.node.ID { 32 | continue 33 | } 34 | //连接远程rpc 35 | rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port) 36 | if err != nil { 37 | fun(false) 38 | continue 39 | } 40 | 41 | var bo = false 42 | err = rp.Call(method, args, &bo) 43 | if err != nil { 44 | fun(false) 45 | continue 46 | } 47 | fun(bo) 48 | } 49 | } 50 | 51 | //投票 52 | func (rf *Raft) Vote(node NodeInfo, b *bool) error { 53 | if rf.votedFor != "-1" || rf.currentLeader != "-1" { 54 | *b = false 55 | } else { 56 | rf.setVoteFor(node.ID) 57 | fmt.Printf("投票成功,已投%s节点\n", node.ID) 58 | *b = true 59 | } 60 | return nil 61 | } 62 | 63 | //确认领导者 64 | func (rf *Raft) ConfirmationLeader(node NodeInfo, b *bool) error { 65 | rf.setCurrentLeader(node.ID) 66 | *b = true 67 | fmt.Println("已发现网络中的领导节点,", node.ID, "成为了领导者!\n") 68 | rf.reDefault() 69 | return nil 70 | } 71 | 72 | //心跳检测回复 73 | func (rf *Raft) HeartbeatRe(node NodeInfo, b *bool) error { 74 | rf.setCurrentLeader(node.ID) 75 | rf.lastHeartBeartTime = millisecond() 76 | fmt.Printf("接收到来自领导节点%s的心跳检测\n", node.ID) 77 | fmt.Printf("当前时间为:%d\n\n", millisecond()) 78 | *b = true 79 | return nil 80 | } 81 | 82 | //追随者节点用来接收消息,然后存储到消息池中,待领导者确认后打印 83 | func (rf *Raft) ReceiveMessage(message Message, b *bool) error { 84 | fmt.Printf("接收到领导者节点发来的信息,id为:%d\n", message.MsgID) 85 | MessageStore[message.MsgID] = message.Msg 86 | *b = true 87 | fmt.Println("已回复接收到消息,待领导者确认后打印") 88 | return nil 89 | } 90 | 91 | //追随者节点的反馈得到领导者节点的确认,开始打印消息 92 | func (rf *Raft) ConfirmationMessage(message Message, b *bool) error { 93 | go func() { 94 | for { 95 | if _, ok := MessageStore[message.MsgID]; ok { 96 | fmt.Printf("raft验证通过,可以打印消息,id为:%d\n", message.MsgID) 97 | fmt.Println("消息为:", MessageStore[message.MsgID], "\n") 98 | rf.lastMessageTime = millisecond() 99 | break 100 | } else { 101 | //如果没有此消息,等一会看看!!! 102 | time.Sleep(time.Millisecond * 10) 103 | } 104 | 105 | } 106 | }() 107 | *b = true 108 | return nil 109 | } 110 | 111 | //领导者接收到,追随者节点转发过来的消息 112 | func (rf *Raft) LeaderReceiveMessage(message Message, b *bool) error { 113 | fmt.Printf("领导者节点接收到转发过来的消息,id为:%d\n", message.MsgID) 114 | MessageStore[message.MsgID] = message.Msg 115 | *b = true 116 | fmt.Println("准备将消息进行广播...") 117 | num := 0 118 | go rf.broadcast("Raft.ReceiveMessage", message, func(ok bool) { 119 | if ok { 120 | num++ 121 | } 122 | }) 123 | 124 | for { 125 | //自己默认收到了消息,所以减去一 126 | if num > raftCount/2-1 { 127 | fmt.Printf("全网已超过半数节点接收到消息id:%d\nraft验证通过,可以打印消息,id为:%d\n", message.MsgID, message.MsgID) 128 | fmt.Println("消息为:", MessageStore[message.MsgID], "\n") 129 | rf.lastMessageTime = millisecond() 130 | fmt.Println("准备将消息提交信息发送至客户端...") 131 | go rf.broadcast("Raft.ConfirmationMessage", message, func(ok bool) { 132 | }) 133 | break 134 | } else { 135 | //休息会儿 136 | time.Sleep(time.Millisecond * 100) 137 | } 138 | } 139 | return nil 140 | } 141 | --------------------------------------------------------------------------------