关键词:Golang、工作量证明、区块链原型币、哈希挖矿、PoW源码、比特币、nonce、创世区块
目录
- 为什么要引入工作量证明
- 工作量证明底层原理 3 句话讲清
- 一行行写链:代码与思路解析
- 动手跑起来:创世区块到第二笔转账
- 常见坑与评分提升
- FAQ:最常见 5 个灵魂拷问
1. 为什么要引入工作量证明
在上一节,我们仅仅把「交易信息」塞进区块并简单计算哈希。节点可以「一秒出块」,整条链难以抵御女巫攻击,也没有办法营造出稀缺价值。
工作量证明(Proof of Work,PoW)就是游戏规则:
- 耗时:CPU/GPU 至少得花几分钟找到满足条件的哈希值。
- 秒验:其他节点 1ms 就能验证。
引入 PoW 后,一条只占用单机资源的原型币也能拥有对标比特币的安全根基。
2. 工作量证明底层原理 3 句话讲清
- 把区块头与 nonce 做二进制拼接,跑 SHA256。
- 要求结果前 N 位全是 0;N 值越大耗时越久。
- 找到合格哈希即出块成功,区块立刻广播,获奖 simpleBTC 10 枚示例。
3. 一行行写链:Go 代码与思路解析
3.1 难度设定:单机不在乎网络调节
我们不会做比特币那种“每 2016 块调整”的动态难度,直接用常量:
const targetBits = 24 // 24 位前导 0 ≈ 6 个十六进制 0这意味着合格哈希 < 2^(256-24),即 0x000001...。
3.2 数据结构升级:新增 Nonce
type Block struct {
Timestamp int64
Data []byte
PrevHash []byte
Hash []byte
Nonce int // 工作量证明轴心字段
}3.3 ProofOfWork 结构体
import "math/big"
type ProofOfWork struct {
Block *Block
Target *big.Int
}
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits)) // 左移构造 256-24 个 0
return &ProofOfWork{b, target}
}如果你还没刷过 math/big,它是 Go 的原生任意精度大整数库,用来存任何挖矿难度目标值刚刚好。3.4 喂数据:把各种字段打成字节流
func (pow *ProofOfWork) prepareData(nonce int) []byte {
return bytes.Join([][]byte{
pow.Block.PrevHash,
pow.Block.Data,
IntToHex(pow.Block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
}, []byte{})
}IntToHex 把 int64 转成大端 8 字节切片,无需累述。
3.5 挖矿主循环
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
nonce := 0
for nonce < maxNonce { // maxNonce 1<<64,单机不会溢出
data := pow.prepareData(nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.Target) == -1 {
fmt.Printf("\r%+v\n", hash)
return nonce, hash[:]
}
nonce++
}
log.Fatal("未能成功挖矿")
return 0, nil
}在这段代码里,hashInt.Cmp(pow.Target) == -1 就是「哈希 < 难度目标」的数学表述,一旦满足即刻出块。
3.6 区块创建时自动挖矿
func NewBlock(data string, prev []byte) *Block {
b := &Block{time.Now().Unix(), []byte(data), prev, nil, 0}
pow := NewProofOfWork(b)
nonce, hash := pow.Run()
b.Hash, b.Nonce = hash, nonce
return b
}3.7 校验函数:二次计算等于一次验证
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.Block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
return hashInt.Cmp(pow.Target) == -1
}在真正的网络环境下,成千上万节点只需跑两次 Hash 就能确认「别人确实负重前行」,这就是 工作量证明效率高到离谱 的佐证。
4. 动手跑起来:创世区块到第二笔转账
func main() {
bc := NewBlockchain() // 构造一条链的创世区块
bc.AddBlock("Send 1 BTC to Lin") // 20 秒左右
bc.AddBlock("Send 2 BTC to Lin") // 再矿一次
for _, block := range bc.Blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevHash)
fmt.Printf("Data: %s\n", string(block.Data))
fmt.Printf("Hash: %x\n", block.Hash)
if pow := NewProofOfWork(block); pow.Validate() {
fmt.Println("PoW: ✔️")
} else {
fmt.Println("PoW: ❌")
}
}
}当终端定格「PoW: ✔️」,第一场链上转账就诞生了。
5. 常见坑与评分提升
| 坑点 | 快速修复 |
|---|---|
| MAC M 系列编译超时 | 用 -tags netgo + CGO_ENABLED=0 静态编译,剪容器镜像不起作用就拿去跑云主机。 |
| 耗时太长 | 把 targetBits 降到 20,把时间从 30s → 3s,方便演示。 |
| 单机最大随机数 2^64 | 实际 Go runtime 在 64 位机器 nonce++ 不会溢出,放心。 |
6. FAQ:最常见 5 个灵魂拷问
Q1:24 比特难度 vs. 比特币 75 比特,差了多少倍?
A:大约 2^51 倍,所以比特币全网得 CPU「万年」出块,单机秒出模拟链。
Q2:为什么不用 scrypt 或 ethash?
A:本文关注「最简」原型与 哈希挖矿 思想,SHA256 已足够。后续可替换。
Q3:Merkle Root 去哪了?
A:本节只存一条交易字符串,直接放 Data 字段。下篇持久化再升级。
Q4:Go 并发挖矿可行吗?
A:完全没问题,用 goroutine 并发跑 Run(),再把有效 nonce 通过 channel 收集即可。注意锁 big.Int 读写。
Q5:接下来要怎么部署到半去中心化环境?
A:👉 把单机链改造成 JSON-RPC 接口,点对点打洞即可跑测试网。
搞定,现在轮到你!
- 拉下代码立刻体验,把
targetBits调成 17,2 秒出块爽到飞起。 - 试试多线程并发挖矿,把哈希算力基准测出来。
- 下一站我们将把区块写进 LevelDB,实现真正的区块链 持久化。提前阅读 👉 LevelDB 每秒可写 400k 次以上,为何不要求你每秒出块 。