1、先直接上代码,有详细注释,然后讲一下如何操作运行这个区块链
[html]
- package main
- import (
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "os"
- "strconv"
- "strings"
- "sync"
- "time"
- "github.com/davecgh/go-spew/spew"
- "github.com/gorilla/mux"
- "github.com/joho/godotenv"
- )
- const difficulty = 1
- // Block 的结构
- type Block struct {
- Index int
- Timestamp string
- BPM int
- Hash string
- PrevHash string
- Difficulty int
- Nonce string
- }
- // Blockchain 定义,即很多区块的集合
- var Blockchain []Block
- // 用来记录脉搏信息的结构
- type Message struct {
- BPM int
- }
- //声明一个互斥量mutex,后面会使用该变量避免出现数据竞争,确保多个区块不会同一时间生成。
- var mutex = &sync.Mutex{}
- func main() {
- err := godotenv.Load()
- if err != nil {
- log.Fatal(err)
- }
- go func() {
- t := time.Now()
- genesisBlock := Block{}
- genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}
- spew.Dump(genesisBlock)
- mutex.Lock()
- Blockchain = append(Blockchain, genesisBlock)
- mutex.Unlock()
- }()
- log.Fatal(run())
- }
- /**
- 快速搭一个Web服务器。首先创建一个run函数,main函数随后会调用这个函数来运行服务器。
- 在makeMuxRouter()中声明了相应的请求处理函数。请注意,我们需要通过GET请求获取区块链,
- 通过POST请求添加新的区块。区块链无法更改,因此我们不需要实现编辑或删除功能。
- 可以通过浏览器访问http://localhost:8080/来访问我们构建的应用。
- */
- 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
- }
- // create handlers
- func makeMuxRouter() http.Handler {
- muxRouter := mux.NewRouter()
- muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
- muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
- return muxRouter
- }
- // write blockchain when we receive an http request
- func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
- bytes, err := json.MarshalIndent(Blockchain, "", " ")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- io.WriteString(w, string(bytes))
- }
- /**
- 这个函数可以实现新区块的添加过程。
- 我们使用Postman来发起POST请求,向http://localhost:8080发送JSON数据(如{“BPM”:60}),
- 其中包含前面你记录下的那个脉搏次数。
- */
- func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- var m Message
- decoder := json.NewDecoder(r.Body)
- if err := decoder.Decode(&m); err != nil {
- respondWithJSON(w, r, http.StatusBadRequest, r.Body)
- return
- }
- defer r.Body.Close()
- //锁定新的区块,避免数据竞争
- mutex.Lock()
- newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
- mutex.Unlock()
- if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
- Blockchain = append(Blockchain, newBlock)
- spew.Dump(Blockchain)
- }
- respondWithJSON(w, r, http.StatusCreated, newBlock)
- }
- /**
- 一旦API调用过程中出现错误就能以JSON格式返回错误信息。
- */
- func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
- w.Header().Set("Content-Type", "application/json")
- 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)
- }
- /**
- 确保我们的索引能正确递增,并且当前区块的PrevHash与前一个区块的Hash相匹配。
- */
- func isBlockValid(newBlock, oldBlock Block) bool {
- if oldBlock.Index+1 != newBlock.Index {
- return false
- }
- if oldBlock.Hash != newBlock.PrevHash {
- return false
- }
- if calculateHash(newBlock) != newBlock.Hash {
- return false
- }
- return true
- }
- /**
- 用HASH256来生成哈希值,以计算Hash以及PrevHash
- */
- func calculateHash(block Block) string {
- record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce
- 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.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
- }
- /**
- 判断计算得到的hash值是否是合适的
- Proof of Work生成的哈希必须具有特定位数的前导零。
- 前导零的位数由程序刚开始定义的difficulty常量来决定(这里这个值为1).
- */
- func isHashValid(hash string, difficulty int) bool {
- prefix := strings.Repeat("0", difficulty)
- return strings.HasPrefix(hash, prefix)
- }
2、运行程序
可以在Golang中直接运行,也可以命令行go run运行,下载源码之后,Readme里面有使用方法
其中的 navigate to this directory and rename the example file `mv example.env .env`
意思是将项目根目录的example.env文件改名为.env,如下图
然后运行程序,通过postman请求,如下图
然后在浏览器中也可以查看区块链数据
登录 | 立即注册