一个基于Web服务器的PoW案例(二)

636 阅读4分钟

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

一个基于web服务器的PoW案例

本文收录于我的专栏:细讲区块链

本专栏会讲述区块链共识算法以及以太坊智能合约、超级账本智能合约、EOS智能合约相关知识,还会详细的介绍几个实战项目。如果有可能的话,我们还能一起来阅读以太坊的源码。有兴趣的话我们一起来学习区块链技术吧~

五、区块校验

func isHashValid(hash string, difficulty int) bool {
    prefix := strings.Repeat("0", difficulty)
    return strings.HasPrefix(hash, prefix)
}

这个我们本专栏之前的文章介绍了,在此简单说一下,这里我们就校验一下哈希值前面的零的数量是不是和难度值一致。

六、启动HTTP服务器

func run() error {
    mux := makeMuxRouter()
    httpAddr := os.Getenv("PORT")
    log.Println("Listening on ", httpAddr)
    
    s := &http.Server{
        Addr: ":" + httpAddr,
        Handler: mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    if err := s.ListenAndServe(); err != nil {
        return err
    }
    return nil
}

我们先从.env文件中获取PORT的值。然后监听获取的端口号。http.Server是设置http服务器的参数,其中Addr是地址,ReadTimeout、WriteTimeout分别是读写超时时间,然后是设置请求头的数据大小的最大值,1 << 20是位运算,算出来就是1MB。!!!最重要的就是回调函数了,这里需要我们自己编写来处理Get和Post请求。

然后我们就来监听事件并且根据监听到的事件来服务。

七、回调函数的编写

func makeMuxRouter() http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/",
        handGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/",
        handWriteBlock).Methods("POST")
    return muxRouter
}

mux.NewRouter()是用来创建路由,muxRouter.HandleFunc("/",handGetBlockchain).Methods("GET")是根据你访问的目录和请求类型来调用指定的方法。这里是使用Get方法访问根目录就调用handGetBlockchain方法。同样的,muxRouter.HandleFunc("/",handWriteBlock).Methods("POST")就是使用Post请求访问根目录时就调用handWriteBlock方法。

八、处理Get请求

func handGetBlockchain(w http.ResponseWriter, r *http.Request) {
    bytes, err := json.MarshalIndent(Blockchain, "", "\t")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    io.WriteString(w, string(bytes))
}

我们需要将数据转换为json格式,便于与前端进行交互。

同样我们的参数分别是响应和请求。然后处理错误,当出现500错误时,也就是http.StatusInternalServerError,我们将err.Error()写入w:

image.png

如果没出错,就将json数据写入w。

九、处理POST请求

func handWriteBlock(writer http.ResponseWriter, request *http.Request) {
	writer.Header().Set("Content-Type", "application/json")
	var message Message
	decoder := json.NewDecoder(request.Body)
	if err := decoder.Decode(&message); err != nil {
		responseWithJSON(writer, request, http.StatusNotFound, request.Body)
	}

	defer request.Body.Close()

	mutex.Lock()
	newBlock := generateBlock(Blockchain[len(Blockchain)-1], message.BPM)
	mutex.Unlock()

	if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
		Blockchain = append(Blockchain, newBlock)
		spew.Dump(Blockchain)
	}
	//返回响应信息
	responseWithJSON(writer, request, http.StatusCreated, newBlock)
}

因为需要服务器响应结果为json,先设置响应头的"Content-Type"为"application/json"。然后从request中读取JSON数据,将JSON数据转成Message。如果转换失败,就交给下一步处理异常,如果成功就创建新的区块。

这里使用defer,说明我们要记得关闭请求哦~

然后添加区块时要记得上锁,可以防止同个时间点多个POST请求生成区块。

接下来就要校验生成的区块是否正确,如果正确就加入区块链中。

十、处理异常

func responseWithJSON(writer http.ResponseWriter, request *http.Request,
    code int, inter interface{}) {
    
    writer.Header().Set("Content-Type", "application/json")
    response, err := json.MarshalIndent(inter, "", "\t")
    if err != nil {
        writer.WriteHeader(http.StatusInternalServerError)
        writer.Write([]byte("HTTP 500:Server Error"))
        return
    }
    writer.WriteHeader(code)
    writer.Write(response)
}

如果将传入的inter转换为json格式的数据没有出现错误就往响应头写入响应码,并将数据写入。

十一、校验区块是否正确

func isBlockValid(newBlock, oldBlock Block) bool {
    if oldBlock.Index+1 != newBlock.Index {
        return false
    }
    if oldBlock.HashCode != newBlock.PreHash {
        return false
    }
    if calculateHash(newBlock) != newBlock.HashCode {
        return false
    }
    return true
}

这里校验了新区块的index是否等于原来最后一个区块的index加一,新区块的PreHash应该等于之前区块链最后一个区块的HashCode。然后还需要再一次计算区块的哈希值,进行比对。

十二、主逻辑

然后我们现在用Go实现通过http请求来完成区块链。

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }
​
    go func() {
        t := time.Now()
        genessisBlock := Block{}
        genessisBlock = Block{0, t.String(),
            0, calculateHash(genessisBlock),
            "", difficulty, 0}
        mutex.Lock()
        Blockchain = append(Blockchain, genessisBlock)
        mutex.Unlock()
        spew.Dump(genessisBlock)
    }()
​
    log.Fatal(run())
}

godotenv.Load()加载一个文件,如果不填写参数,就默认是加载.env文件。

这个.env文件我们这里就只需要填写一个端口号。

image-20211112171257325

这里我们先将创世区块加入区块链。然后用spew.Dump()将其格式化输出到命令行。

最后我们会要用run来启动http服务器。

十三、运行结果

我们可以使用curl来进行get和post请求。

image-20211112212602740

这是get请求,得到区块链。

image-20211112212642066

这是进行post请求,新建一个区块加到了区块链。

image-20211112212757976

可以看到再次get请求,已经有新的区块在区块链中了。