r语言error in match.fun(fun) :_Go语言200行写区块链源代码分析

Github上有一个Repo,是一个使用Go语言(golang),不到200行代码写的区块链源代码,准确的说是174行。原作者起了个名字是 Code your own blockchain in less than 200 lines of Go! 而且作者也为此写了一篇文章。https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc

这篇文章是一个大概的思路和代码的实现,当然还有很多代码的逻辑没有涉及,所以我就针对这不到200行的代码进行一个分析,包含原文章里没有涉及到的知识点,对Go语言,区块链都会有一个更深的认识。

所有的源代码都在这里:https://github.com/nosequeldeebee/blockchain-tutorial/blob/master/main.go

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"
    "github.com/joho/godotenv"
)

在源代码的开头,是作者引入的一些包,有标准的,也有第三方的。像sha256,hex这些标准包是为了sha-256编码用的,其他还有启动http服务,打印日志的log,并发控制的sync,时间戳的time。

第三方包有三个,其中两个我都详细介绍过,相信大家不会陌生。

go-spew是一个变量结构体的调试利器,可以打印出变量结构体对应的数据和结构,调试非常方便

gorilla/mux是一个web路由服务,可以很简单的帮我们构建web服务。

不过目前用gin的比较多,也推荐使用gin https://github.com/gin-gonic/gin。

godotenv是一个读取配置文章的库,可以让我们读取.env格式的配置文件,比如从配置文件里读取IP、PORT等。不过目前配置文件还是推荐YAML和TOML,对应的第三方库是:

gopkg.in/yaml.v21https://github.com/BurntSushi/toml

既然要写一个区块链,那么肯定的有一个区块的实体,我们通过golang的struct来实现。

// Block represents each 'item' in the blockchain
type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
}

Block里包含几个字段:

  1. Index 就是Block的顺序索引

  2. Timestamp是生成Block的时间戳

  3. BPM,作者说代表心率,每分钟心跳数

  4. Hash是通过sha256生成的散列值,对整个Block数据的Hash

  5. PrevHash 上一个Block的Hash,这样区块才能连在一起构成区块链

有了区块Block了,那么区块链就非常好办了。

// Blockchain is a series of validated Blocks
var Blockchain []Block

就是这么简单,一个Block数组就是一个区块链。区块链的构成关键在于Hash和PrevHash,通过他们一个个串联起来,就是一串Block,也就是区块链。因为相互之间通过Hash和PrevHash进行关联,所以整个链很难被篡改,链越长被篡改的成本越大,因为要把整个链全部改掉才能完成篡改的目的,这样的话,其他节点验证这次篡改肯定是不能通过的。

既然关键点在于Hash,所以我们要先算出来一个Block的数据的Hash,也就是对Block里的字段进行Hash,计算出一个唯一的Hash值。

// SHA256 hasing
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)
}

sha256是golang内置的sha256的散列标准库,可以让我们很容易的生成对应数据的散列值。从源代码看,是把Block的所有字段进行字符串拼接,然后通过sha256进行散列,散列的数据再通过hex.EncodeToString转换为16进制的字符串,这样就得到了我们常见的sha256散列值,类似这样的字符串8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

Block的散列值被我们计算出来了,Block的Hash和PrevHash这两个字段搞定了,那么我们现在就可以生成一个区块了,因为其他几个字段都是可以自动生成的。

// 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.Hash = calculateHash(newBlock)

    return newBlock
}

因为区块链是顺序相连的,所以我们在生成一个新的区块的时候,必须知道上一个区块,也就是源代码里的oldBlock。另外一个参数BPM就是我们需要在区块里存储的数据信息了,这里作者演示的例子是心率,我们可以换成其他业务中想要的数据。

Index是上一个区块的Index+1,保持顺序;Timestamp通过time.Now()可以得到;Hash通过calculateHash方法计算出来。这样我们就生成了一个新的区块。

在这里作者并没有使用POW(工作量证明)这类算法来生成区块,而是没有任何条件的,这里主要是为了模拟区块的生成,演示方便。

区块可以生成了,但是生成的区块是否可信,我们还得对他进行校验,不能随便生成一个区块。在比特币(BitCoin)中校验比较复杂,这里作者采用了简单模拟的方式。

// 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
}

简单的对比Index,Hash是否是正确的,并且重新计算了一遍Hash,防止被篡改。

到了这里,关于区块链的代码已经全部完成了,剩下的就是把区块链的生成、查看等包装成一个Web服务,可以通过API、浏览器访问查看。因为作者这里没有实现P2P网络,所以采用的是WEB服务的方式。

// create handlers
func makeMuxRouter() http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

通过mux定义了两个Handler,URL都是/,但是对应的Method是不一样的。

GET方法通过handleGetBlockchain函数实现,用于获取区块链的信息。

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))
}

Blockchain是一个[]BlockhandleGetBlockchain函数的作用是把Blockchain格式化为JSON字符串,然后显示出来。io.WriteString是一个很好用的函数,可以往Writer里写入字符串。更多参考 Go语言实战笔记(十九)| Go Writer 和 Reader

'POST'方法通过handleWriteBlock函数实现,用于模拟区块的生成。

func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    //使用了一个Mesage结构体,更方便的存储BPM
    var msg Message

    //接收请求的数据信息,类似{"BPM":60}这样的格式
    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)

}

以上代码我进行了注释,便于理解。主要是通过POST发送一个{"BPM":60}格式的BODY来添加区块,如果格式正确,那么就生成区块进行校验,合格了就加入到区块里;如果格式不对,那么返回错误信息。

用于控制并发的锁可以参考Go语言实战笔记(十七)| Go 读写锁

这个方法里有个Message结构体,主要是为了便于操作方便。

// Message takes incoming JSON payload for writing heart rate
type Message struct {
    BPM int
}

返回的JSON信息,也被抽取成了一个函数respondWithJSON,便于公用。

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)
}

好了,快完成了,以上Web的Handler已经好了,现在我们要启动我们的Web服务了。

// 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
}

和原生的http.Server基本一样,应该比较好理解。mux其实也是一个Handler,这就是整个Handler处理链。现在我们就差一个main主函数来启动我们整个程序了。

//控制并发的锁
var mutex = &sync.Mutex{}

func main() {
    //加载env配置文件
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

    //开启一个goroutine生成一个创世区块
    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())

}

整个main函数并不太复杂,主要就是加载env配置文件,开启一个go协程生成一个创世区块并且添加到区块链的第一个位置,然后就是通过run函数启动Web服务。

一个区块链都有一个创世区块,也就是第一个区块。有了第一个区块我们才能添加第二个,第三个,第N个区块。创世区块因为是第一个区块,所以它是没有PrevHash的。

终于可以运行了,假设我们设置的PORT是8080,现在我们通过go run main.go启动这个简易的区块链程序,就可以看到控制台输出的创世区块信息。然后我们通过浏览器打开http://localhost:8080也可以看到这个区块链的信息,里面只有一个创世区块。

如果我们要新增一个区块,通过curl或者postman,向http://localhost:8080 发送body格式为{"BPM":60}的POST的信息即可。然后在通过浏览器访问http://localhost:8080查看区块链信息,验证是否已经添加成功。

到这里,整个源代码的分析已经完了,我们看下这个简易的区块链涉及到多少知识:

  1. sha256散列

  2. 字节到16进制转换

  3. 并发同步锁

  4. Web服务

  5. 配置文件

  6. 后向式链表

  7. 结构体

  8. JSON

  9. ……

等等,上面的很多知识,我已经在文章中讲解或者通过以前些的文章说明,大家可以看一下,详细了解。


推荐阅读

  • go语言的开源区块链代码都有哪些?欢迎留言补充

学习交流 Go 语言,扫码回复「进群」即可

2c1d4b2d403f2c36b2dc7c8f37de5dfc.png

站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验

Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注

e167204e79528e7ea8d69c26f9d89dda.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

方案的写法

转至野路子吴鲁加的网络日志方案的写法今天看《胡雪岩》第二册P530提道&#xff1a;像这些“说贴”&#xff0c;最要紧的是简洁&#xff0c;要几句话就能把那些大官儿说动心&#xff0c;才是上品……这个说帖&#xff0c;王有龄、赵景贤一定会看完&#xff0c;但递到黄宗汉手中…

oracle distinct分页优化_Oracle SQL性能优化最常用的40条建议

关于Oracle SQL优化的内容&#xff0c;这一篇应该能满足常规大部分的应用优化需求&#xff0c;整整40条优化建议&#xff0c;干货满满。1. SQL语句执行步骤语法分析> 语义分析> 视图转换 >表达式转换> 选择优化器 >选择连接方式 >选择连接顺序 >选择数据的…

Python的文件

今天我们主要来讲一下关于Python文件的一些知识&#xff1a; 1、当我们先打开一个文件的时候&#xff0c;在Python中可以这样子做&#xff1a;使用open()函数&#xff0c;大概类似于这样&#xff1a;f open(lsf.txt)&#xff0c;你应该知道txt是记事本文件的后缀吧&#xff0c…

必须理解的分布式系统中雷同的集群技术及原理

写在前面 在当今信息爆炸的时代&#xff0c;单台计算机已经无法负载日益增长的业务发展&#xff0c;虽然也有性能强大的超级计算机&#xff0c;但是这种高端机不仅费用高昂&#xff0c;也不灵活&#xff0c;一般的企业是负担不起的&#xff0c;而且也损失不起&#xff0c;那么将…

如何获取.then的返回值_仅仅知道如何终止XHR请求,或许对你来说是不够的!

TLDR:当我们需要的时候&#xff0c;我们可以通过AbortController接口来终止一个或者多个请求。前言到目前为止&#xff0c;我们有两个常用的基本的手段去发送请求进而局部刷新页面内容&#xff0c;其一是XMR(XMLHttpRequest)&#xff0c;其二是fetch&#xff0c;我们一个个说XH…

Java - 常用工具类 - 集合框架

转载于:https://www.cnblogs.com/wnzhong/p/9309371.html

stunnel使用详解

http://www.stunnel.org/faq/args.htmlhttp://www.colasoft.com.cn/support/monitor_stunnel.php科来网络分析系统与stunnel结合使用科来网络分析系统是一款强大的网络检测分析工具&#xff0c;可对网络中未加密的数据传输进行检测分析并实时显示分析结果&#xff0c;包括用户的…

binlog日志_【删库跑路】使用Binlog日志恢复误删的MySQL数据

前言“删库跑路”是程序员经常谈起的话题&#xff0c;今天&#xff0c;我就要教大家如何删&#xff01;库&#xff01;跑&#xff01;路&#xff01;开个玩笑&#xff0c;今天文章的主题是如何使用Mysql内置的Binlog日志对误删的数据进行恢复&#xff0c;读完本文&#xff0c;你…

Java并发编程笔记之ThreadLocal内存泄漏探究

使用 ThreadLocal 不当可能会导致内存泄露&#xff0c;是什么原因导致的内存泄漏呢&#xff1f; 我们首先看一个例子&#xff0c;代码如下&#xff1a; /*** Created by cong on 2018/7/14.*/ public class ThreadLocalOutOfMemoryTest {static class LocalVariable {private L…

Res协议,专题

打造完美的&#xff29;&#xff25;网页木马 发表&#xff1a;2004-5-20 19:41:00 出处&#xff1a;你的博客网(yourblog.org) 打造完美的&#xff29;&#xff25;网页木马 icyfox &#xff08;测试页面:http://www.godog.y365.com/runexe/icyfox.htm&#xff0c;此处所…

springboot中获取bean_最新Spring Boot干货总结(超详细,建议收藏)

前言&#xff1a;本文非常长&#xff0c;建议先mark后看&#xff0c;也许是最后一次写这么长的文章说明&#xff1a;前面有4个小节关于Spring的基础知识分别是&#xff1a;IOC容器、JavaConfig、事件监听、SpringFactoriesLoader详解它们占据了本文的大部分内容&#xff1a;虽然…

python全栈学习总结三:函数学习

一函数基本概念 1 函数定义 def 函数名&#xff08;参数1&#xff0c;参数2&#xff0c;参数3&#xff0c;......&#xff09;: """ 功能&#xff1a; 参数1&#xff1a; 参数2&#xff1a; 参数3&#xff1a; ...... 返回值&#xff1a; """ 函…

街机模拟器联网简谈

Kawaks联网教程现在大多数模拟器的联网功能主要是靠"kaillera"来实现的,要想实现联网玩游戏,你必须有一个kaillera的客户端和一个服务器端的程序,客户端现在大多在模拟器中自带,而服务器端则是一个单独的程序.kaillera有多种操作系统,而大多是在windows下,好了废话不…

freeRTOS V10.0.1移植到STM32F407标准库 - 环境Keil5

最近因为工作需要用到FreeRTOS&#xff0c;其实开始本人内心是拒绝的因为自己只学习过UCOSIII还没实际上过什么大又复杂的工程&#xff0c;但是谁让FreeRTOS他是Free的呢公司成本考虑肯定是不会选择USOS的&#xff0c;这个道理就像公司内心深处不想给你涨工资一样。好了跑偏了言…

破解静态WEP KEY全过程

破解静态WEP KEY全过程广州卓讯盟科技有限公司 黄超毅Email:huangchaoyigzpia.com/demonalexdark2s.org发现首先通过NetStumbler确认客户端已在某AP的覆盖区内&#xff0c;并通过AP信号的参数进行‘踩点’&#xff08;数据搜集&#xff09;。NetStumbler 下载地址 http://www.n…

apicloud项目怎么运行_Spring Boot教程(3) – 运行第一个项目

把项目运行起来是极具成就感的事情&#xff0c;不管是在学习新语言还是新框架的过程中。上一篇文章说明了如何使用Spring Initializr和IDE来创建项目&#xff0c;下面就看看项目能不能跑起来&#xff0c;我们通过IDE和命令行&#xff0c;直观地理解框架的作用。先来一张Intelli…

Matlab从入门到精通 Chapter5 数据可视化

5-1 图形绘制示例 >> x2-17:0.02:3;>> y21./((x23).^21)1./((x29).^24)5; >> subplot(2,2,2);>> plot(x2,y2,rp);>> axis([-17 3 5 6.5]);>> title(figure2);>> grid on subplot 子图表绘制函数 默认情况下&#xff0c;plot函数将绘…

破解WEP密钥过程全解 (下)

三、实战破解过程1、用Kismet进行网络探测Kismet是一个基于Linux的无线网络扫描程序&#xff0c;这是一个相当方便的工具&#xff0c;通过测量周围的无线信号来找到目标WLAN。虽说Kismet也可以捕获网络上的数据通信&#xff0c;但在还有其他更好的工具使用(如Airodump)&#xf…

mpAndroidchart 坐标和图表距离_【玩转图表系列】六步,美化你的图表,让老板刮目相看!...

近期隆重推出图表分析系列&#xff0c;包括销售分析、盈亏分析、费用分析、趋势分析、进度分析等&#xff0c;通过双坐标图、甘特图、瀑布图、双层饼图等系列图表精美展现&#xff0c;专业高效&#xff0c;让你从初级学到高级、从小白跨越专业。今天开启我们邦邦财玩转图表第一…

SpringBoot+FreeMarker开发word文档下载,预览

背景&#xff1a; 开发一个根据模版&#xff0c;自动填充用户数据并下载word文档的功能 使用freemarker进行定义模版&#xff0c;然后把数据进行填充。 maven依赖&#xff1a; <parent> <groupId>org.springframework.boot</groupId> <artifactId>…