从第一行代码开始开发区块链(二)

本文介绍如何使用Go语言构建区块链,包括链表结构、工作量证明(PoW)机制及UTXO模型。通过Go的数组和切片实现区块链表,借助sha256包计算hash值。此外,详细讲解了PoW的实现,通过循环计算找到满足难度目标的hash值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


传送门: 柏链项目学院



如何通过go语言打造区块链

为什么选择go语言呢?因为个人兴趣爱好,作为后端语言go确实比c++要舒服一些,此外go语言对加密算法,hash函数支持的也非常好。

我们要支持哪些功能?
  • 有区块的链表
  • pow 共识机制
  • UTXO模型
1. 有区块的链表

go语言里借助数组或切片就可以模拟有序链表,所以直接用切片即可,一个区块包含哪些信息呢?

type Block struct {
	Timestamp     int64  // 时间戳 类似 1546590891
	Data          []byte // 打包的交易数据,我们可以随意模拟
	PrevBlockHash []byte // 前一块hash值
	Hash          []byte // 当前hash值
}

那么hash如何计算呢?可以把前一块hash,本块数据,时间戳等进行加工(join)计算,最后借助sha256包的sum256函数获得本块hash值。

    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
	hash := sha256.Sum256(headers)

上述代码中 strconv.FormatInt(b.Timestamp, 10) 就是将时间戳转换为字符串,最后通过[]byte强制转换为[]byte类型。

type Blockchain struct {
	blocks []*Block
}

上述定义就代表我们有了一个区块链,是不是太简单了!接下来编写如何添加区块!

func (bc *Blockchain) AddBlock(data string) {
	prevBlock := bc.blocks[len(bc.blocks)-1]
	newBlock := NewBlock(data, prevBlock.Hash)
	bc.blocks = append(bc.blocks, newBlock)
}

我们这样测试,并且打印区块链结果

func main() {
	bc := NewBlockchain()

	bc.AddBlock("Send 1 BTC to Ivan")
	bc.AddBlock("Send 2 more BTC to Ivan")

	for _, block := range bc.blocks {
		fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
		fmt.Printf("Data: %s\n", block.Data)
		fmt.Printf("Hash: %x\n", block.Hash)
		fmt.Println()
	}
}

ok,到这里我们完成了一个最初版本的开发。

全部代码如下:

  • main.go
package main

import (
	"fmt"
)

func main() {
	bc := NewBlockchain()

	bc.AddBlock("Send 1 BTC to Ivan")
	bc.AddBlock("Send 2 more BTC to Ivan")

	for _, block := range bc.blocks {
		fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
		fmt.Printf("Data: %s\n", block.Data)
		fmt.Printf("Hash: %x\n", block.Hash)
		fmt.Println()
	}
}
  • block.go
package main

import (
	"bytes"
	"crypto/sha256"
	"strconv"
	"time"
)

// Block keeps block headers
type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
}

// SetHash calculates and sets block hash
func (b *Block) SetHash() {
	timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
	headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
	hash := sha256.Sum256(headers)

	b.Hash = hash[:]
}

// NewBlock creates and returns Block
func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
	block.SetHash()
	return block
}

// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock() *Block {
	return NewBlock("Genesis Block", []byte{})
}
  • blockchain.go
package main

// Blockchain keeps a sequence of Blocks
type Blockchain struct {
	blocks []*Block
}

// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
	prevBlock := bc.blocks[len(bc.blocks)-1]
	newBlock := NewBlock(data, prevBlock.Hash)
	bc.blocks = append(bc.blocks, newBlock)
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
	return &Blockchain{[]*Block{NewGenesisBlock()}}
}
2. 产生区块的工作量证明

之前我们的代码已经可以生成区块了,但是产生区块太容易了,这样任何人都可以添加一个区块,给记账也带来混乱。前面我们也提到了,想要在链表中增加区块,那么必须做一个数学难题,这个难题就是找到一个适合的数字能让它产生一个符合条件的hash值,而且这个hash值必须小于某个数。计算出这个hash值对应的数字没有投机取巧的办法,只能自己尝试,谁先拿到了,谁就中奖了。当然,这个事儿可以靠计算能力来作弊,举个简单的例子,假设一共有256个值需要尝试,你的计算能力如果是别人的4倍,比如你有四台机器同时计算,那么你中奖的机会也就是别人的4倍。下面还是来说代码,需要对之前的代码改造。

在产生区块时需要经过一个hash计算,而且这个值必须小于一个数,习惯上把它成为挖矿难度。

var (
	maxNonce = math.MaxInt64
)

const targetBits = 24

// ProofOfWork represents a proof-of-work
type ProofOfWork struct {
	block  *Block
	target *big.Int
}

math.MaxInt64 实际上是1左移63位后-1

MaxInt64  = 1<<63 - 1

targetBits 实际上就是挖矿难度了,需要通过这个挖矿难度最后再计算出一个数。

接下来我们实现生成ProofOfWork结构体的函数

func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))
	fmt.Println("target======", target)

	pow := &ProofOfWork{b, target}

	return pow
}

将前一块的内容加上本块数据结合起来,准备去挖矿

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

	return data
}

循环实验,也就是挖矿,hashInt.Cmp 是如果hashInt小于target则返回-1,这样就ok了。

func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce := 0

	fmt.Printf("Mining the block containing \"%s\"maxNonce=%d\n", pow.block.Data, maxNonce)
	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[:]
}

顺便我们再增加一个验证的函数,验证是否挖到矿

func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int

	data := pow.prepareData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])

	isValid := hashInt.Cmp(pow.target) == -1

	return isValid
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值