Go语言简单实现区块链

199 阅读6分钟

一、区块链介绍

区块链是一种基于密码学保障和P2P网络的分布式记账技术。在如今Web3.0时代下,去中心化的潮流已经势不可挡,区块链技术更是其中的一项关键技术。

区块链的三大技术

  1. 分布式账本
  2. 共识机制
  3. 密码学
  4. 智能合约

本文实现的区块链系统的共识机制是最为常见和经典的PoW(工作量证明),其他的共识机制还有PoS(股权证明)、DPoS(委托权益证明)和BFT(拜占庭容错算法)等。

二、准备工作

Go环境安装

官网地址:golang.org/dl/

国内打不开的可以打开这个地址:golang.google.cn/dl/

根据自己的系统选择合适的安装包,安装后复制安装所在路径到环境变量中

在终端输入检查是否安装成功:

go version

编译环境

Visual Studio Code、Goland等开发环境

VSCode需安装拓展(在拓展搜索Go,第一个即是)

三、一个基本的区块链

区块链的结构是一串单向的链表,一个区块的由上一个区块的哈希值和本区块的哈希值形成连接,这样确保了链上的任一区块的改变都将会被检测到

我们来构建一个基本的区块:

import(
 "fmt"
 "crypto/sha256"
 "encoding/hex"
 "strconv"
)
type Block struct {
    previousHash string
    hash string
    metadata []string
    timestamp string
}

func Hash(s string)(string) {
    hash := sha256.Sum256([]byte(s))
    return hex.EncodeToString(hash[:])
}

func calculateHash(block *Block)(string) {
    str := block.timestamp + strconv.Itoa(block.nonce)
    return Hash(str)
}

为了计算哈希值,我们引用了Go基本库中的crypto库,我们定义一个名为Block的结构体,储存了上一个区块的哈希值、本区块的哈希值和时间戳。

随后创建一个链:

var latestPointer *string

type BlockChain struct {
    chain []Block
}

func createGenesisBlock(c *BlockChain) {
    var genesisBlock Block
    genesisBlock.timestamp = "01/01/2023"
    genesisBlock.metadata = []string {"null"}
    genesisBlock.hash = calculateHash(&genesisBlock)
    genesisBlock.previousHash = ""
    genesisBlock.nonce = 0
    c.chain = append(c.chain, genesisBlock)
}

func addBlock(timestamp string, c *BlockChain){
    if len(c.chain) == 0 {
        createGenesisBlock(c)
    }
    latesPointer = &c.chain[len(c.chain)-1].hash
    var newBlock Block
    newBlock.timestamp = timestamp
    newBlock.hash = calculateHash(&newBlock)
    newBlock.previousHash = *latesPointer
    newBlock.metadata = metaData
    c.chain = append(c.chain, newBlock)
    latesPointer = &c.chain[len(c.chain)-1].hash
}

在BlockChian构造体中,我们用一个数组储存传递过来的区块,并且我们定义一个全局的用于储存上一个区块的哈希值的地址,我们还设置了两个方法:

  • createGenesisBlock方法用于创建创世块,是区块链的首个区块。(小知识:中本聪于UTC时间2009年1月13日18:15:05生成了世界上第一个区块)
  • addBlock方法用于添加块到链上,首先检测链是否为空,否则创建创世块。更新上一个区块的哈希值到latesPointer中,设置区块的时间戳、哈希值和上一个区块的哈希值,添加到链上,最后再重新更新一下指针。

总结:

我们实现一个基本的区块链,但这个区块链是不安全的。

我们在下一步将解决这两个问题:

  1. 一致性问题:如何确保添加到链上的区块是安全的(不是恶意修改的信息)
  2. 垃圾信息:所有人都可以在短时间内快速的将区块上传到链上,若有一个节点发送大量的区块将导致系统的崩溃。

四、PoW工作量证明

什么是PoW:

PoW可以理解成我们熟知的挖矿,计算机分配给每个节点一个数学问题,每个节点需计算出正确答案才可以上传区块,并且每个节点都可以验证这个答案是正确的。其目的在于使作恶者作恶的成本大于获得利益,而作恶者要想赶在其他所有节点计算出正确答案,需要拥有全世界51%的算力,这所需的成本大于其获得利益,有效的解决了我们在上一节提出的两个问题。

实现方法:

我们先来修改一下Block结构体中的成员

type Block struct {
    previousHash string
    hash string
    metadata []string
    timestamp string
    nonce int \\我们添加一个成员属性nonce
}

func calculateHash(block *Block) string {
    str := calculateRootHash(pendingTransaction) + block.timestamp + strconv.Itoa(block.nonce)
    return Hash(str)

}

同时也需要一个方法来进行所谓的挖矿:

func mineBlock(b *Block,difficulty int){
    for true {
        if b.hash[:difficulty] == strings.Repeat("0",difficulty){
            break
        }else{
            b.nonce += 1
            b.hash = calculateHash(b)
        }
    }
    fmt.Println("Block mined: "+ b.hash)
}

通过设置difficulty来控制计算难度,每次挖矿就是计算机在不断尝试直至得出符合条件的答案。

随后我们将其应用到区块链上,完整代码为:

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "strconv"
    "strings"
)

var latesPointer *string

type Block struct {
    previousHash string
    hash string
    metadata []string
    timestamp string
    nonce int
}

type BlockChain struct {
    chain []Block
}

func calculateHash(block *Block) string {
    str := calculateRootHash(pendingTransaction) + block.timestamp + strconv.Itoa(block.nonce)
    return Hash(str)
}

func mineBlock(b *Block,difficulty int){
    for true {
        if b.hash[:difficulty] == strings.Repeat("0",difficulty){
            break
        }else{
            b.nonce += 1
            b.hash = calculateHash(b)
        }
    }
    fmt.Println("Block mined: "+ b.hash)
}

func createGenesisBlock(c *BlockChain){
    var genesisBlock Block
    genesisBlock.timestamp = "01/01/2023"
    genesisBlock.metadata = []string {"null"}
    genesisBlock.hash = calculateHash(&genesisBlock)
    genesisBlock.previousHash = ""
    genesisBlock.nonce = 0
    c.chain = append(c.chain, genesisBlock)
}

func addBlock(timestamp,metaData string, c *BlockChain){
    if pendingTransaction != nil {
        if len(c.chain) == 0 {
            createGenesisBlock(c)
        }
        latesPointer = &c.chain[len(c.chain)-1].hash
        var newBlock Block
        newBlock.nonce = 0
        newBlock.timestamp = timestamp
        newBlock.hash = calculateHash(&newBlock)
        newBlock.metadata = metaData
        newBlock.previousHash = *latesPointer
        mineBlock(&newBlock,5)
        c.chain = append(c.chain, newBlock)
        latesPointer = &c.chain[len(c.chain)-1].hash
        pendingTransaction = nil
    }
}

问题

我们创建了一个有基本功能的区块链,但是将区块上传到区块链所需时间较久(比特币一般是十分钟),如果在这期间有多个节点上传区块将造成堵塞。

五、事务管理

为了解决上一节提出的问题,我们可以增加一个待处理事务的流程,同时增加交易类

var pendingTransaction []string

type Transcation struct {
    fromAdress string
    toAdress string
    amount int
}

func addTransaction(t *Transcation){
    //验证交易信息
    transaction := t.fromAdress + t.toAdress + strconv.Itoa(t.amount)
    pendingTransaction = append(pendingTransaction, transaction)
}

在新增了一个交易结构体后,为了对区块计算哈希值的方法继续改进,更好的增强区块链的安全性,我们先计算待处理事务的默克尔树。同时我们将每个交易信息做Base64编码

func calculateRootHash(pt []string)(string){
    for len(pt)>1{
        if len(pt)%2 != 0{
            pt = append(pt, "")
        }
        var ram []string
        for i := 0; i < len(pt)/2; i++ {
            ram = append(ram, Hash(pt[2*i]+pt[(2*i)+1]))
        }
        pt = ram
    }
    return pt[0]
}

func basa64(pd []string)([]string){
    var str []string
    for i := 0; i < len(pd); i++{
        code := base64.StdEncoding.EncodeToString([]byte(pd[i]))
        str = append(str,code)
    }
    return str
}

最终,我们的addBlock方法如下:

func addBlock(timestamp string, c *BlockChain){
    if pendingTransaction != nil {    //确保添加块时待处理事务不为空
        if len(c.chain) == 0 {
            createGenesisBlock(c)
        }
        latesPointer = &c.chain[len(c.chain)-1].hash
        var newBlock Block
        newBlock.nonce = 0
        newBlock.timestamp = timestamp
        newBlock.hash = calculateHash(&newBlock)
        newBlock.metadata = basa64(pendingTransaction) //储存base64编码后的元数据
        newBlock.previousHash = *latesPointer
        mineBlock(&newBlock,5)
        c.chain = append(c.chain, newBlock)
        latesPointer = &c.chain[len(c.chain)-1].hash
        pendingTransaction = nil
    }
}

完整的源代码如下:

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "strconv"
    "encoding/base64"
    "strings"
)

var pendingTransaction []string

type Transcation struct {
    fromAdress string
    toAdress string
    amount int
}

type Block struct {
    previousHash string
    hash string
    metadata []string
    timestamp string
    nonce int
}

type BlockChain struct {
    chain []Block
}

func Hash(s string)(string){
    hash := sha256.Sum256([]byte(s))
    return hex.EncodeToString(hash[:])
}

func addTransaction(t *Transcation){
    transaction := t.fromAdress + t.toAdress + strconv.Itoa(t.amount)
    pendingTransaction = append(pendingTransaction, transaction)
}  

func calculateRootHash(pt []string)(string){
    for len(pt)>1{
        if len(pt)%2 != 0{
            pt = append(pt, "")
        }
        var ram []string
        for i := 0; i < len(pt)/2; i++ {
            ram = append(ram, Hash(pt[2*i]+pt[(2*i)+1]))
        }
        pt = ram
    }
    return pt[0]
}

func calculateHash(block *Block) string {
    str := calculateRootHash(pendingTransaction) + block.timestamp + strconv.Itoa(block.nonce)
    return Hash(str)
}

func basa64(pd []string)([]string){
    var str []string
    for i := 0; i < len(pd); i++{
        code := base64.StdEncoding.EncodeToString([]byte(pd[i]))
        str = append(str,code)
    }
    return str
}

func createGenesisBlock(c *BlockChain){
    var genesisBlock Block
    genesisBlock.timestamp = "01/01/2023"
    genesisBlock.metadata = []string {"null"}
    genesisBlock.hash = calculateHash(&genesisBlock)
    genesisBlock.previousHash = ""
    genesisBlock.nonce = 0
    c.chain = append(c.chain, genesisBlock)
}

func mineBlock(b *Block,difficulty int){
    for true {
        if b.hash[:difficulty] == strings.Repeat("0",difficulty){
            break
        }else{
            b.nonce += 1
            b.hash = calculateHash(b)
        }
    }
    fmt.Println("Block mined: "+ b.hash)
}

var latesPointer *string

func addBlock(timestamp string, c *BlockChain){
    if pendingTransaction != nil 
        if len(c.chain) == 0 {
            createGenesisBlock(c)
        }
        latesPointer = &c.chain[len(c.chain)-1].hash
        var newBlock Block
        newBlock.nonce = 0
        newBlock.timestamp = timestamp
        newBlock.hash = calculateHash(&newBlock)
        newBlock.metadata = basa64(pendingTransaction)
        newBlock.previousHash = *latesPointer
        mineBlock(&newBlock,5)
        c.chain = append(c.chain, newBlock)
        latesPointer = &c.chain[len(c.chain)-1].hash
        pendingTransaction = nil
    }
}

声明

该代码实现的区块链仅仅实现了最为基本的功能,最重要的P2P网络和密码学保障功能还没实现。本人水平浅薄,代码写的烂,在此抛砖引玉,欢迎大家在评论区指教和批评,感谢您的阅读。