200行golang 实现的区块链

    科技2022-07-16  117

    下面的代码实现了一个最小的区块链。访问使用Http接口

    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 represents each 'item' in the blockchain type Block struct { Index int // 区块高度,创世区块是0,新生成区块依次+1 Timestamp string // 本区块创建的时间戳 BPM int // 区块存储的数据,在比特币中是交易数据 Hash string // 本区块哈希值 PrevHash string // 前一个区块的哈希值 Difficulty int // 区块的挖矿难度 Nonce string // 挖到本区块的碰撞值(随机值),用于其它节点挑战本区块的真实性 } // Blockchain is a series of validated Blocks。存储所有区块(内存),在比特币中是存储在数据库中(硬盘) var Blockchain []Block // Message takes incoming JSON payload for writing heart rate。区块(交易)数据 type Message struct { BPM int } var mutex = &sync.Mutex{} func main() { err := godotenv.Load("example.env") 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 server 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)) } // takes JSON payload as an input for heart rate (BPM) 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() //ensure atomicity when creating new block 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) } 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) } // make 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 calculateHash(newBlock) != newBlock.Hash { return false } return true } // SHA256 hasing 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) } // create a new block using previous block's 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 // 挖矿:在区块中加入一个变量i,使用sha256计算对应的哈希值,符合条件就算挖到矿(哈希前面有连续difficulty个0) 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 } func isHashValid(hash string, difficulty int) bool { prefix := strings.Repeat("0", difficulty) return strings.HasPrefix(hash, prefix) }

    测试:

    Processed: 0.009, SQL: 8