区块链入门
in Study on Blockchain, Golang
前言
我的小朋友又开始搞新花样了,说要做区块链溯源的项目,虽然听起来很高大上,其实她连原理还没怎么掌握,为了不误人子弟,我先自己学习了一下区块链的基础知识。
说语言选择的问题,觉得golang现在很热门,而且优点数不胜数,比如多线程,效率高。相比python js,我跟希望使用golang来实现。所以最开始先熟悉了一下golang的语法 才开始学习区块链相关。
概念以及基础入门
golang基础入门 mux 基础入门 了解flag包
了解spew包/
了解godotenv包
了解sync包
区块链工作量证明
blockchain-tutorial
代码分析
最简单的区块链
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"strconv" //包转换 错误处理
"sync" //提供基本的同步原语
"time"
"github.com/davecgh/go-spew/spew" //数据显示
"github.com/gorilla/mux" //简易http服务器
"github.com/joho/godotenv" //隔离环境变量到配置文件
)
// Block 一个区块
type Block struct {
Index int //在整个链中的索引
Timestamp string //时间戳
BPM int //心跳
Hash string //哈希
PrevHash string //上一个块的哈希
}
//Blockchain 区块链是一系列可验证的块
var Blockchain []Block
// 计算块的哈希 块的哈希是使用自己生成的 所以能保证数据可验证
func calculateHash(block Block) string {
record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash //将自己的整型值转换为字符串
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed) //得到该块的hash
}
// 利用原有的块生成新块
func generateBlock(oldBlock Block, BPM int) Block {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock
}
// 校验块
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index { //如果旧块的索引加一不是新块false
return false
}
if oldBlock.Hash != newBlock.PrevHash { //如果旧块的哈希不是新块中记录的上一个哈希false
return false
}
if calculateHash(newBlock) != newBlock.Hash { //计算出的哈希不是当前块中记录的哈希false
return false
}
return true
}
//至此 我们构建出了最简单的区块链
// Message 通过json携带了心跳
type Message struct {
BPM int
}
//互斥锁
var mutex = &sync.Mutex{}
//主函数
func main() {
err := godotenv.Load() //初始化godotenv
if err != nil {
log.Fatal(err)
}
go func() { //多线程
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""} //生成创世块
spew.Dump(genesisBlock) //展示创世块
mutex.Lock() //防止多个创世块被添加到同一区块链
Blockchain = append(Blockchain, genesisBlock) //把创世块添加到区块链上
mutex.Unlock()
}()
log.Fatal(run())
}
// 创建Web服务器
func run() error {
mux := makeMuxRouter()
httpPort := os.Getenv("PORT")
log.Println("HTTP Server Listening on port :", httpPort)
s := &http.Server{
Addr: ":" + httpPort,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
// 创建 handlers
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
// 当接收到http请求时写入区块链
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
bytes, err := json.MarshalIndent(Blockchain, "", " ") //MarshalIndent类似于Marshal,但应用Indent格式化输出。
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}
// 将JSON载荷作为心跳(BPM)的输入
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var msg Message
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&msg); err != nil {
respondWithJSON(w, r, http.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
mutex.Lock() //写入块的时候加锁
prevBlock := Blockchain[len(Blockchain)-1]
newBlock := generateBlock(prevBlock, msg.BPM)
if isBlockValid(newBlock, prevBlock) { //生成块之后还需要验证一下
Blockchain = append(Blockchain, newBlock)
spew.Dump(Blockchain)
}
mutex.Unlock()
respondWithJSON(w, r, http.StatusCreated, newBlock)
}
//http返回json
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
response, err := json.MarshalIndent(payload, "", " ")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("HTTP 500: Internal Server Error"))
return
}
w.WriteHeader(code)
w.Write(response)
}
// 怎么玩:
// 首先新建.env文件 然后写入PORT=[端口号]
// POST方式以JSON传入{"BPM":[int]}写块 再get方式查看区块链即可
工作量证明
const difficulty = 1
// Block 区块新增难度和Nonce来确保工作量
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
Difficulty int
Nonce string
}
//generateBlock 生成区块时需要计算达到要求
func generateBlock(oldBlock Block, BPM int) Block {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Difficulty = difficulty
for i := 0; ; i++ {
hex := fmt.Sprintf("%x", i)
newBlock.Nonce = hex
if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
fmt.Println(calculateHash(newBlock), " do more work!")
time.Sleep(time.Second)
continue
} else {
fmt.Println(calculateHash(newBlock), " work done!")
newBlock.Hash = calculateHash(newBlock)
break
}
}
return newBlock
}
//isHashValid 难度系数即下一个hash开头有多少位0
func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}
P2P
以后有机会再更