区块链生态及技术栈

0.学习目的

goland作为开发工具,用go语言进行区块链系统的模拟运行与开发。

1.区块以及区块链的实现

区块链中的区块通常有以下几个基本属性:

Data:本身存储的数据

Hash:根据本身DataPrevhash由算法计算的来的哈希值

Prevhash:上一个区块的哈希值

Timestamp:时间戳

其余数据有待补充,我们先根据这四个基本的属性来建立一个类Block作为构筑区块链的基础区块。

package block
import (
"bytes"
"crypto/sha256"
"time"
)
type Block struct {
Hash []byte
Data []byte
PrevHash []byte
Timestamp int64
}

根据定义我们了解到,区块中最重要的数据是哈希值,需结合已有的数据来计算出来,我们可以将数据Data,前一个块的哈希值PrevHash,和当前块的时间戳Timestam,连接在一起来构建一个字节数组info

使用SHA-256哈希函数对info进行哈希计算,使用sha256.Sum256()函数将计算出的哈希值存储在hash变量中。这个哈希值是一个长度为32字节的字节数组。

最后将计算出的哈希值切片赋值给当前块的Hash字段,以便在后续使用。

func (b *Block) deriveHash() {
info := bytes.Join([][]byte{b.Data, b.PrevHash, []byte(string(b.Timestamp))}, []byte{})
hash := sha256.Sum256(info)
b.Hash = hash[:]
}

接下来写一个方法并调用derivehash()方法以创建区块。

func CreateBlock(data string, prevHash []byte) *Block {
block := &Block{[]byte{}, []byte(data),prevHash ,time.Now().Unix()}
block.deriveHash()
return block
}

区块链就是一个集合了众多区块的结构体,构筑区块链就是在每个区块的第一个区块("创世区块")后面不断添加新的区块,我们先用上述的方法构建创世区块Genesis

func Genesis() *Block {
return CreateBlock("Genesis", []byte{})
}

接下来定义结构体区块链并将创世区块作为第一个区块,等待添加的区块以构建区块链。

type BlockChain struct {
Blocks []*Block
}
func InitBlockChain() *BlockChain {
return &BlockChain{[]*Block{Genesis()}}
}

现在需要一个方法帮助我们在创世区块后面添加新块,根据我们对于区块的定义,我们对该方法有这样的要求:它接收一个data参数,该参数是要存储在新区块中的数据。首先获取区块链中的最后一个区块(即当前链的最新区块)作为prevBlock。然后,使用CreateBlock()函数创建一个新的区块,该区块的数据为传入的data,前一个区块的哈希值为prevBlock.Hash。最后,将新的区块追加到区块链的Blocks字段中,以实现向区块链添加新区块的功能。

func (chain *BlockChain) AddBlock(data string) {
prevBlock := chain.Blocks[len(chain.Blocks)-1]
new := CreateBlock(data, prevBlock.Hash)
chain.Blocks = append(chain.Blocks, new)
}

至此我们有了进行一系列后续实验的基础,现在来检验一下成果。

package main
import (
block "bcts"
"fmt"
)
func blockchainRun() {
chain := block.InitBlockChain()
chain.AddBlock("First Block after Genesis")
chain.AddBlock("Second Block after Genesis")
chain.AddBlock("Third Block after Genesis")
for _, block := range chain.Blocks {
fmt.Printf("Previous Hash: %x\n", block.PrevHash)
fmt.Printf("Data in Block: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Printf("time: %d\n",block.Timestamp)
}
}
func main() {
blockchainRun()
}

我们将会得到以下结果:

至此搭建了一个我们自己的区块链作为我们实验的工具。

2.模拟区块链账本数据结构实现

如果我们把区块链应用在账本领域,可以做一个简单的理解:
大家都有着更改账本的权力,假设每次大家的改动都已经得到了承认是合法的。就形成了一条记载了交易信息的区块链,如果有人篡改了数据,会造成恶劣的影响,我们需要一个数据结构去验证区块链中的区块是否合法。

在上文中我们得知,每一个区块加入到链中,会有直接取得的上一个区块的哈希值作为自己的prevhash,也存储由data配合prevhash而生成的hash,那么我们可以从这两个方面入手去验证区块链的合法性。

如果我们查到现在区块记录下的prevhash与上一个区块的hash不同,我们就可以得知,区块链的数据被改变了。

让我们先在区块链里加入两个实验用的区块。

var firstdata string
var seconddata string
fmt.Println("请输入第一个区块的信息")
fmt.Scan(&firstdata)
fmt.Println("请输入第二个区块的信息")
fmt.Scan(&seconddata)
chain :=block.InitBlockChain()
chain.AddBlock(firstdata)
chain.AddBlock(seconddata)

再来检查一下前后关系。

for i := 1; i < len(chain.Blocks); i++ {
currentBlock := chain.Blocks[i]
prevBlock := chain.Blocks[i-1]
if !bytes.Equal(currentBlock.PrevHash, prevBlock.Hash) {
fmt.Println("前后区块链断裂")
break
}
if i == len(chain.Blocks)-1{
fmt.Println("区块链结构正常")
break
}
}

正常情况下我们会得到这样的结果:

我们可以修改一下区块链中的顺序,已验证一下我们的机制是否有效。

chain := block.InitBlockChain()
chain.AddBlock("Block 1 Data")
chain.AddBlock("Block 2 Data")
chain.AddBlock("Block 3 Data")
changedata(chain)
changerank(chain)
chain.Blocks[1].Data = []byte("Modified Data")
changedata(chain)
changerank(chain)

这里是我们的完整代码:

package main
import (
block "bcts"
"bytes"
"fmt"
)
func changerank(chain *block.BlockChain){
for i := 1; i < len(chain.Blocks); i++ {
currentBlock := chain.Blocks[i]
prevBlock := chain.Blocks[i-1]
if !bytes.Equal(currentBlock.PrevHash, prevBlock.Hash) {
fmt.Println("前后区块链断裂")
break
}
if i == len(chain.Blocks)-1{
fmt.Println("区块链结构正常")
break
}
}
}
func changemax(){
chain := block.InitBlockChain()
chain.AddBlock("Block 1 Data")
chain.AddBlock("Block 2 Data")
chain.AddBlock("Block 3 Data")
changerank(chain)
fmt.Println("现在修改第一个区块的数据")
chain.Blocks[1].Data = []byte("Modified Data")
chain.Blocks[1].DeriveHash()
changerank(chain)
}
func main(){
changemax()
}

这里是我们的运行结果:

至此我们有了一个简易的证明区块链是否合法的机制,用于简易的账本结构数据实现。

3.POW:工作量证明机制

在我们前两个章节的工作中, 我们构建了一个很简单的数据结构,这个结构就是区块链数据库的本质。而且我们赋予了它们类似于链式操作中添加数据块的能力:每个区块和前一个区块相链接。不过我们发现,我们的区块链实现有一个很大的瑕疵:添加一个区块太简单了,成本太低了。区块链和比特币的其中一个重要基石则是添加新的区块非常困难。现在我们可以引入工作量证明机制(Proof-of-Work,PoW)来解决这个问题。

如果某个客户想要改变区块链里区块的数据,我们就要求他进行一些耗时适当的复杂运算,并且答案能被服务方快速验算,把此过程耗用的时间、设备与能源做为担保成本,以确保服务与资源是被真正的需求所使用。

工作量使用的核心技术是散列函数,我们想象一个函数f(X) = x^18+cos(x)+ln(9x)+sin(x).......(一系列复杂的公式),如果我们得知x,可以用计算机技术很快的计算出结果,但如果要在得知f(x)的情况下倒推x,就会是一个极其复杂的问题,计算机只能通过穷举方法来不断尝试,以不断接近需要的答案,这个过程就是在进行工作量证明。

SHA256函数举例,假设我们要处理资料Hello World,并找出f(n)前四值为0000的n,如果从Hello World0开始加上一个十进制ASCII进行穷举猜测,到Hello World107105时才会得到符合条件的f(n):

0000BFE6AF4232F78B0C8EBA37A6BA6C17B9B8671473B0B82305880BE077EDD9

所以在这里,我们要对区块添加两个新的属性:

difficulty:修改该区块链的难度。

nonce:用于计算散列函数的随机值。

而我们由散列函数的例子得知,区块对于难度的调节体现在目标哈希值前几位是0,在我们编程时,可以直接切入到这个方面来调整难度。

在真正的区块链中,目标难度会动态改变,在这里我们先把它设为一个全局常量。

const targetBits = 16 // 定义目标哈希的位数
type ProofOfWork struct {
block *Block
target *big.Int
}
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
pow := &ProofOfWork{b, target}
return pow
}

我们引入了big 包来进行高精度的整数运算,它允许我们超出内置整数类型的范围限制。

我们把targetBits定义为16,这是规定前四位为0,我们可以将这个值加减4来改变0的数量。

结合上面hello world的例子,我们要将区块的Datanonce组合在一起并不断增加nonce来实现工作量证明:

func (pow *ProofOfWork) prepareData(nonce int64) []byte {
data := bytes.Join(
[][]byte{
pow.block.Data,
pow.block.PrevHash,
IntToHex(pow.block.Timestamp),
IntToHex(nonce),
IntToHex(int64(targetBits)),
},
[]byte{},
)
return data
}

接下来我们来写核心算法:

func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("进行工作量证明中 \"%s\"\n", pow.block.Data)
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}

在引入nonce这一全新概念之后,我们需要把他重新加入到我们的区块中,来改写我们的blockCreateblock方法:

type Block struct {
Hash []byte
Data []byte
PrevHash []byte
Timestamp int64
Nonce int64
}
func CreateBlock(data string, prevHash []byte) *Block {
block := &Block{[]byte{}, []byte(data),prevHash ,time.Now().Unix(),0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Nonce = nonce
block.Hash = hash[:]
return block
}

在之前的方法中我们转换哈希成一个 big integer,现在写一个算法然后和目标值对比是不是比它小。

func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.block.Timestamp)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
return hashInt.Cmp(pow.target) == -1
}

现在我们完成了全部的准备工作,来验证一下我们的结果吧。

package main
import (
block "bcts"
"fmt"
)
func main() {
chain := block.InitBlockChain()
chain.AddBlock("Block 1 Data")
chain.AddBlock("Block 2 Data")
chain.AddBlock("Block 3 Data")
for _, b := range chain.Blocks {
fmt.Printf("Prev Hash: %x\n", b.PrevHash)
fmt.Printf("Data: %s\n", b.Data)
fmt.Printf("Hash: %x\n", b.Hash)
pow := block.NewProofOfWork(b)
fmt.Printf("PoW: %v\n", pow.Validate())
fmt.Println()
}
}

这是我们的运行结果,很不幸实验用的区块们全失败了。

posted @   TAX1118  阅读(233)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示