社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
1.新建main.go文件
package main
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net"
"os"
"strconv"
"sync"
"time"
"github.com/davecgh/go-spew/spew" //spew包将区块链输出到终端
"github.com/joho/godotenv" //godotenv从前面的.env文件中读取配置信息
)
// Block represents each 'item' in the blockchain
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
Validator string //由原先的difficulty改为了现在validator,即记账节点的地址
}
// Blockchain is a series of validated Blocks
var Blockchain []Block
var tempBlocks []Block
// tempBlocks是临时存储单元,在区块被选出来并添加到BlockChain之前,临时存储在这里
// candidateBlocks handles incoming blocks for validation
var candidateBlocks = make(chan Block)
//chan建立的是一个fifo队列,fifo先进先出
//candidateBlocks 是 Block 的通道,任何一个节点在提出一个新块时都将它发送到这个通道
// announcements broadcasts winning validator to all nodes
var announcements = make(chan string)
// announcements也是一个通道,主Go TCP服务器将向所有节点广播最新的区块链
var mutex = &sync.Mutex{}
//是一个标准变量,允许我们控制读/写和防止数据竞争
// validators keeps track of open validators and balances
var validators = make(map[string]int)
//validators 是节点的存储地址,同时也会保存每个节点持有的令牌数
//calculateBlockHash函数将一个block的所有字段连接到一起后,再调用 calculateHash 将字符串进行求hash。
// SHA256 hasing
// calculateHash is a simple SHA256 hashing function
func calculateHash(s string) string {
h := sha256.New()
h.Write([]byte(s))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
//calculateBlockHash returns the hash of all block information
func calculateBlockHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
return calculateHash(record)
}
//与前一个区块的hash值,生成区块
func generateBlock(oldBlock Block, BPM int, address string) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String() //时间戳
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateBlockHash(newBlock)
newBlock.Validator = address
return newBlock, nil
}
//验证区块内容
// isBlockValid makes sure block is valid by checking index
// and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}
if oldBlock.Hash != newBlock.PrevHash {
return false
}
if calculateBlockHash(newBlock) != newBlock.Hash {
return false
}
return true
}
func handleConn(conn net.Conn) {
defer conn.Close()
//defer语句延迟执行一个函数,该函数被推迟到当包含它的程序return时再执行。
go func() {
for {
msg := <-announcements //通道的值赋给msg
io.WriteString(conn, msg)
}
}()
var address string // 验证者地址,即Validator的地址
// 验证者输入他所拥有的 tokens,tokens 的值越大,越容易获得新区块的记账权
// allow user to allocate number of tokens to stake
// the greater the number of tokens, the greater chance to forging a new block
io.WriteString(conn, "Enter token balance:")
scanBalance := bufio.NewScanner(conn) //在终端输入获取token余额
// 允许验证者输入他持有的令牌数量,然后,该验证者被分配一个 SHA256地址,随后该验证者地址和验证者的令牌数被添加到验证者列表validators 中。
for scanBalance.Scan() { // scanBalance.Scan()遇到 n 或者rn循环一次,每输入一行内容必须回车,退出循环按组合键 Ctrl +D
balance, err := strconv.Atoi(scanBalance.Text()) //strconv包实现字符串到数字的转换
if err != nil {
log.Printf("%v not a number: %v", scanBalance.Text(), err)
return
}
t := time.Now()
address = calculateHash(t.String())
validators[address] = balance
//余额赋给validators集合,集合的键值为其地址即哈希值,address为对时间序列求的hash值,防止有相同的address
fmt.Println(validators)
break
}
io.WriteString(conn, "nEnter a new BPM:")
scanBPM := bufio.NewScanner(conn) //BPM可以看作区块上要记录的数据
go func() {
for {
// take in BPM from stdin and add it to blockchain after conducting necessary validation
for scanBPM.Scan() {
bpm, err := strconv.Atoi(scanBPM.Text())
// if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens
//对作恶者进行惩罚(即输入),删除他作为一个记录者的权利,并扣除他的余额
if err != nil {
log.Printf("%v not a number: %v", scanBPM.Text(), err)
delete(validators, address)
//delete()函数从map中删除一组键值对,delete(map, 键)
conn.Close()
}
mutex.Lock() //互斥锁,即在一个并发进程中,加锁中的语句只能在解锁后再执行,不能并发同时执行
oldLastIndex := Blockchain[len(Blockchain)-1]
mutex.Unlock()
// create newBlock for consideration to be forged
newBlock, err := generateBlock(oldLastIndex, bpm, address)
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, oldLastIndex) {
candidateBlocks <- newBlock
}
io.WriteString(conn, "nEnter a new BPM:")
}
}
}()
//一直输出区块信息数据
// simulate receiving broadcast
for {
time.Sleep(time.Minute) //休眠1分钟
mutex.Lock()
output, err := json.Marshal(Blockchain)
mutex.Unlock()
if err != nil {
log.Fatal(err)
}
io.WriteString(conn, string(output)+"n")
}
}
//选择获取记账权的节点
// pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain
// by random selecting from the pool, weighted by amount of tokens staked
func pickWinner() {
time.Sleep(30 * time.Second) //休眠30秒钟
mutex.Lock()
temp := tempBlocks
mutex.Unlock()
lotteryPool := []string{}
if len(temp) > 0 {
// slightly modified traditional proof of stake algorithm
// from all validators who submitted a block, weight them by the number of staked tokens
// in traditional proof of stake, validators can participate without submitting a block to be forged
OUTER:
for _, block := range temp {
// if already in lottery pool, skip
for _, node := range lotteryPool {
if block.Validator == node {
continue OUTER
}
}
// lock list of validators to prevent data race
mutex.Lock()
setValidators := validators
mutex.Unlock()
k, ok := setValidators[block.Validator]
if ok {
for i := 0; i < k; i++ {
lotteryPool = append(lotteryPool, block.Validator)
}
}
}
// randomly pick winner from lottery pool
s := rand.NewSource(time.Now().Unix()) //time.Now().Unix()将时间戳转化为时间格式
//rand.NewSource()生成伪随机数 time.Now().Unix()为种子值
// 使用当前的时间生成一个随机源,也就是随机种子
r := rand.New(s)
// 生成一个rand
lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))]
//r.Intn 返回一个随机的格式化后的时间n,0 <= n <=len(lotteryPool)
// add block of winner to blockchain and let all the other nodes know
for _, block := range temp {
if block.Validator == lotteryWinner {
mutex.Lock()
Blockchain = append(Blockchain, block)
mutex.Unlock()
for _ = range validators {
announcements <- "nwinning validator: " + lotteryWinner + "n"
}
break
}
}
}
mutex.Lock()
tempBlocks = []Block{}
mutex.Unlock()
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
// create genesis block
//初始化创世区块
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""}
spew.Dump(genesisBlock)
//使用spew.Dump 这个函数可以以非常美观和方便阅读的方式将 struct、slice 等数据打印在控制台里,方便我们调试。
Blockchain = append(Blockchain, genesisBlock)
//将创世区块添加到结构体数组Blockchain中
// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+os.Getenv("ADDR")) //os.Getenv()从文件中获取环境变量
if err != nil {
log.Fatal(err)
}
defer server.Close()
go func() {
for candidate := range candidateBlocks {
mutex.Lock()
tempBlocks = append(tempBlocks, candidate)
mutex.Unlock()
}
}()
go func() {
for { //无限循环下去
pickWinner()
}
}()
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}
2.新建 .env文件,并写入以下代码
ADDR=9000
3.运行main.go文件
export GOPATH=/home/go
go run main.go
4.进入9000端口
nc localhost 9000
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!