区块链生态及技术栈
0.学习目的
以goland
作为开发工具,用go语言进行区块链系统的模拟运行与开发。
1.区块以及区块链的实现
区块链中的区块通常有以下几个基本属性:
Data
:本身存储的数据
Hash
:根据本身Data
和Prevhash
由算法计算的来的哈希值
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的例子,我们要将区块的Data
和nonce
组合在一起并不断增加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
这一全新概念之后,我们需要把他重新加入到我们的区块中,来改写我们的block
和Createblock
方法:
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() } }
这是我们的运行结果,很不幸实验用的区块们全失败了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!