区块链入门

前言

我的小朋友又开始搞新花样了,说要做区块链溯源的项目,虽然听起来很高大上,其实她连原理还没怎么掌握,为了不误人子弟,我先自己学习了一下区块链的基础知识。
说语言选择的问题,觉得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

以后有机会再更


© 2021. All rights reserved.

本站总访问量 Web Analytics

Powered by Hydejack v9.1.2 & Moded by ZYA