Go 与区块链:从概念到实现
区块链,这个听起来很神秘的技术,其实核心思想非常简单:去中心化的、不可篡改的数据账本。你可能听说过比特币、以太坊,但你知道区块链的底层原理吗?
今天,我们将用 Go 语言从零开始构建一个简单的区块链系统,深入理解区块链的核心概念:区块、哈希、工作量证明、交易、钱包和 P2P 网络。
区块链基础概念
在开始写代码之前,让我们先理解几个关键概念:
- 区块(Block):包含交易数据的容器
- 哈希(Hash):区块的数字指纹,用于链接区块
- 工作量证明(Proof of Work):确保区块链安全的共识机制
- 交易(Transaction):从一个地址到另一个地址的价值转移
- 钱包(Wallet):管理私钥和公钥的工具
- P2P 网络:去中心化的节点通信网络
实现区块结构
首先,让我们定义区块的基本结构:
package blockchain
import (
"bytes"
"crypto/sha256"
"encoding/gob"
"time"
)
// Block 区块结构
type Block struct {
Timestamp int64 // 区块创建时间
Transactions []*Transaction // 交易列表
PrevBlockHash []byte // 前一个区块的哈希
Hash []byte // 当前区块的哈希
Nonce int // 工作量证明的计数器
Height int // 区块高度
}
// Serialize 序列化区块
func (b *Block) Serialize() ([]byte, error) {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
err := encoder.Encode(b)
if err != nil {
return nil, err
}
return result.Bytes(), nil
}
// DeserializeBlock 反序列化区块
func DeserializeBlock(d []byte) (*Block, error) {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
err := decoder.Decode(&block)
if err != nil {
return nil, err
}
return &block, nil
}
// NewBlock 创建新区块
func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {
block := &Block{
Timestamp: time.Now().Unix(),
Transactions: transactions,
PrevBlockHash: prevBlockHash,
Hash: []byte{},
Nonce: 0,
Height: height,
}
// 计算区块哈希(后面会用 PoW 替代)
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Nonce = nonce
block.Hash = hash
return block
}
// GenesisBlock 创建创世区块
func GenesisBlock(coinbase *Transaction) *Block {
return NewBlock([]*Transaction{coinbase}, []byte{}, 0)
}
// HashTransactions 计算区块中所有交易的哈希
func (b *Block) HashTransactions() []byte {
var txHashes [][]byte
for _, tx := range b.Transactions {
txHashes = append(txHashes, tx.ID)
}
// 使用 Merkle Tree 会更高效,这里简化处理
txHash := sha256.Sum256(bytes.Join(txHashes, []byte{}))
return txHash[:]
}
实现工作量证明(PoW)
工作量证明是区块链的核心机制,它确保创建新区块需要付出计算成本,从而防止恶意节点随意篡改数据。
package blockchain
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"log"
"math"
"math/big"
)
const (
targetBits = 16 // 哈希前 16 位必须是 0
maxNonce = math.MaxInt64
)
// ProofOfWork 工作量证明
type ProofOfWork struct {
block *Block
target *big.Int
}
// NewProofOfWork 创建 PoW 实例
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
return &ProofOfWork{b, target}
}
// prepareData 准备用于哈希计算的数据
func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.HashTransactions(),
IntToHex(pow.block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
},
[]byte{},
)
return data
}
// Run 执行工作量证明
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining block with %d transactions\n", len(pow.block.Transactions))
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
// 找到有效的 nonce
fmt.Printf("\rHash: %x\n", hash)
return nonce, hash[:]
}
// 显示进度
if nonce%100000 == 0 {
fmt.Printf("\rMining... Nonce: %d, Hash: %x", nonce, hash)
}
nonce++
}
return nonce, nil
}
// Validate 验证工作量证明
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
}
// IntToHex 将整数转换为十六进制字节数组
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
实现交易结构
交易是区块链的核心,它记录了价值从一个地址到另一个地址的转移。
package blockchain
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"fmt"
"math/big"
"strings"
)
const subsidy = 10 // 挖矿奖励
// Transaction 交易结构
type Transaction struct {
ID []byte // 交易 ID
Vin []TXInput // 输入
Vout []TXOutput // 输出
}
// TXInput 交易输入
type TXInput struct {
Txid []byte // 引用之前的交易 ID
Vout int // 引用的输出索引
Signature []byte // 签名
PubKey []byte // 公钥
}
// TXOutput 交易输出
type TXOutput struct {
Value int // 金额
PubKeyHash []byte // 接收者公钥哈希
}
// IsCoinbase 检查是否为 coinbase 交易(挖矿奖励)
func (tx *Transaction) IsCoinbase() bool {
return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}
// Serialize 序列化交易
func (tx *Transaction) Serialize() []byte {
var encoded bytes.Buffer
enc := gob.NewEncoder(&encoded)
err := enc.Encode(tx)
if err != nil {
fmt.Printf("Error serializing transaction: %v\n", err)
return nil
}
return encoded.Bytes()
}
// DeserializeTransaction 反序列化交易
func DeserializeTransaction(data []byte) Transaction {
var transaction Transaction
decoder := gob.NewDecoder(bytes.NewReader(data))
err := decoder.Decode(&transaction)
if err != nil {
fmt.Printf("Error deserializing transaction: %v\n", err)
}
return transaction
}
// Hash 计算交易哈希
func (tx *Transaction) Hash() []byte {
var hash [32]byte
txCopy := *tx
txCopy.ID = []byte{}
hash = sha256.Sum256(txCopy.Serialize())
return hash[:]
}
// SetID 设置交易 ID
func (tx *Transaction) SetID() {
tx.ID = tx.Hash()
}
// Sign 签名交易
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
// 验证所有输入引用的交易是否存在
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
fmt.Printf("ERROR: Previous transaction not found\n")
return
}
}
// 创建交易的副本用于签名
txCopy := tx.TrimmedCopy()
for inID, vin := range txCopy.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
dataToSign := fmt.Sprintf("%x\n", txCopy)
r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))
if err != nil {
fmt.Printf("Error signing transaction: %v\n", err)
return
}
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
txCopy.Vin[inID].PubKey = nil
}
}
// Verify 验证交易签名
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
if tx.IsCoinbase() {
return true
}
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
fmt.Printf("ERROR: Previous transaction not found\n")
return false
}
}
txCopy := tx.TrimmedCopy()
curve := elliptic.P256()
for inID, vin := range tx.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}
dataToVerify := fmt.Sprintf("%x\n", txCopy)
if !ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) {
return false
}
txCopy.Vin[inID].PubKey = nil
}
return true
}
// TrimmedCopy 创建交易的精简副本
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _, vin := range tx.Vin {
inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
}
for _, vout := range tx.Vout {
outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
}
txCopy := Transaction{tx.ID, inputs, outputs}
return txCopy
}
// NewCoinbaseTX 创建 coinbase 交易
func NewCoinbaseTX(to, data string) *Transaction {
if data == "" {
randData := make([]byte, 20)
_, err := rand.Read(randData)
if err != nil {
fmt.Printf("Error generating random data: %v\n", err)
return nil
}
data = fmt.Sprintf("%x", randData)
}
txin := TXInput{[]byte{}, -1, nil, []byte(data)}
txout := NewTXOutput(subsidy, to)
tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}}
tx.SetID()
return &tx
}
// NewUTXOTransaction 创建普通交易
func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction {
var inputs []TXInput
var outputs []TXOutput
pubKeyHash := HashPubKey(wallet.PublicKey)
acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount {
fmt.Println("ERROR: Not enough funds")
return nil
}
// 构建输入
for txid, outs := range validOutputs {
txID, err := hex.DecodeString(txid)
if err != nil {
fmt.Printf("Error decoding txid: %v\n", err)
return nil
}
for _, out := range outs {
input := TXInput{txID, out, nil, wallet.PublicKey}
inputs = append(inputs, input)
}
}
// 构建输出
from := fmt.Sprintf("%s", wallet.GetAddress())
outputs = append(outputs, *NewTXOutput(amount, to))
if acc > amount {
// 找零
outputs = append(outputs, *NewTXOutput(acc-amount, from))
}
tx := Transaction{nil, inputs, outputs}
tx.SetID()
UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
// String 交易的字符串表示
func (tx Transaction) String() string {
var lines []string
lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID))
for i, input := range tx.Vin {
lines = append(lines, fmt.Sprintf(" Input %d:", i))
lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid))
lines = append(lines, fmt.Sprintf(" Vout: %d", input.Vout))
lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature))
lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey))
}
for i, output := range tx.Vout {
lines = append(lines, fmt.Sprintf(" Output %d:", i))
lines = append(lines, fmt.Sprintf(" Value: %d", output.Value))
lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash))
}
return strings.Join(lines, "\n")
}
// NewTXOutput 创建交易输出
func NewTXOutput(value int, address string) *TXOutput {
txo := &TXOutput{value, nil}
txo.Lock([]byte(address))
return txo
}
// Lock 锁定输出(设置公钥哈希)
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := Base58Decode(address)
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
out.PubKeyHash = pubKeyHash
}
// IsLockedWithKey 检查输出是否被指定公钥锁定
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
// UsesKey 检查输入是否使用了指定公钥
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := HashPubKey(in.PubKey)
return bytes.Compare(lockingHash, pubKeyHash) == 0
}
// HashPubKey 计算公钥哈希
func HashPubKey(pubKey []byte) []byte {
version := []byte{0x00}
pubKeyHash := sha256.Sum256(pubKey)
// 这里简化了 RIPEMD160,实际应该使用
// ripemd160 := sha256.Sum256(pubKeyHash[:])
versionedPayload := append(version, pubKeyHash[:]...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
return fullPayload
}
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:4]
}
实现钱包
钱包管理用户的私钥和公钥,用于签名交易。
package blockchain
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"golang.org/x/crypto/ripemd160"
)
const version = byte(0x00)
const addressChecksumLen = 4
// Wallet 钱包
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
// NewWallet 创建新钱包
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
// GetAddress 获取钱包地址
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
// HashPubKey 计算公钥哈希(Bitcoin 风格)
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
if err != nil {
fmt.Printf("Error hashing public key: %v\n", err)
return nil
}
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
// ValidateAddress 验证地址是否有效
func ValidateAddress(address string) bool {
pubKeyHash := Base58Decode([]byte(address))
actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
version := pubKeyHash[0]
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen]
targetChecksum := checksum(append([]byte{version}, pubKeyHash...))
return bytes.Compare(actualChecksum, targetChecksum) == 0
}
// newKeyPair 生成新的密钥对
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
fmt.Printf("Error generating key pair: %v\n", err)
}
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
// Base58Encode Base58 编码
func Base58Encode(input []byte) []byte {
alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
result := []byte{}
x := new(big.Int).SetBytes(input)
zero := big.NewInt(0)
mod := new(big.Int)
for x.Cmp(zero) > 0 {
x.DivMod(x, big.NewInt(58), mod)
result = append([]byte{alphabet[mod.Int64()]}, result...)
}
// 处理前导零
for _, b := range input {
if b == 0x00 {
result = append([]byte{alphabet[0]}, result...)
} else {
break
}
}
return result
}
// Base58Decode Base58 解码
func Base58Decode(input []byte) []byte {
alphabet := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
result := big.NewInt(0)
for _, char := range input {
index := bytes.IndexByte([]byte(alphabet), char)
if index == -1 {
return nil
}
result.Mul(result, big.NewInt(58))
result.Add(result, big.NewInt(int64(index)))
}
tmpBytes := result.Bytes()
var numZeros int
for numZeros = 0; numZeros < len(input); numZeros++ {
if input[numZeros] != alphabet[0] {
break
}
}
flength := numZeros + len(tmpBytes)
decoded := make([]byte, flength, flength)
copy(decoded[numZeros:], tmpBytes)
return decoded
}
实现区块链
现在让我们把所有组件组合成完整的区块链:
package blockchain
import (
"encoding/hex"
"fmt"
"os"
"github.com/dgraph-io/badger"
)
const dbPath = "./tmp/blocks"
const blocksBucket = "blocks"
// Blockchain 区块链
type Blockchain struct {
tip []byte // 最新区块的哈希
db *badger.DB
}
// BlockchainIterator 区块链迭代器
type BlockchainIterator struct {
currentHash []byte
db *badger.DB
}
// AddBlock 添加新区块
func (bc *Blockchain) AddBlock(transactions []*Transaction) {
var lastHash []byte
err := bc.db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("l"))
if err != nil {
return err
}
lastHash, err = item.ValueCopy(nil)
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error getting last hash: %v\n", err)
return
}
// 获取最后一个区块的高度
var lastHeight int
err = bc.db.View(func(txn *badger.Txn) error {
item, err := txn.Get(lastHash)
if err != nil {
return err
}
encodedBlock, err := item.ValueCopy(nil)
if err != nil {
return err
}
lastBlock, err := DeserializeBlock(encodedBlock)
if err != nil {
return err
}
lastHeight = lastBlock.Height
return nil
})
if err != nil {
fmt.Printf("Error getting last block: %v\n", err)
return
}
// 创建新区块
newBlock := NewBlock(transactions, lastHash, lastHeight+1)
err = bc.db.Update(func(txn *badger.Txn) error {
encodedBlock, err := newBlock.Serialize()
if err != nil {
return err
}
err = txn.Set(newBlock.Hash, encodedBlock)
if err != nil {
return err
}
err = txn.Set([]byte("l"), newBlock.Hash)
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error adding block: %v\n", err)
}
}
// Iterator 创建区块链迭代器
func (bc *Blockchain) Iterator() *BlockchainIterator {
return &BlockchainIterator{bc.tip, bc.db}
}
// Next 获取下一个区块
func (i *BlockchainIterator) Next() *Block {
var block *Block
err := i.db.View(func(txn *badger.Txn) error {
item, err := txn.Get(i.currentHash)
if err != nil {
return err
}
encodedBlock, err := item.ValueCopy(nil)
if err != nil {
return err
}
block, err = DeserializeBlock(encodedBlock)
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error getting block: %v\n", err)
return nil
}
i.currentHash = block.PrevBlockHash
return block
}
// FindUnspentTransactions 查找未花费的交易
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
var unspentTXs []Transaction
spentTXOs := make(map[string][]int)
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID)
Outputs:
for outIdx, out := range tx.Vout {
if spentTXOs[txID] != nil {
for _, spentOut := range spentTXOs[txID] {
if spentOut == outIdx {
continue Outputs
}
}
}
if out.IsLockedWithKey(pubKeyHash) {
unspentTXs = append(unspentTXs, *tx)
}
}
if !tx.IsCoinbase() {
for _, in := range tx.Vin {
if in.UsesKey(pubKeyHash) {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
}
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return unspentTXs
}
// FindUTXO 查找未花费的交易输出
func (bc *Blockchain) FindUTXO() map[string]TXOutputs {
UTXO := make(map[string]TXOutputs)
spentTXOs := make(map[string][]int)
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID)
Outputs:
for outIdx, out := range tx.Vout {
if spentTXOs[txID] != nil {
for _, spentOutIdx := range spentTXOs[txID] {
if spentOutIdx == outIdx {
continue Outputs
}
}
}
outs := UTXO[txID]
outs.Outputs = append(outs.Outputs, out)
UTXO[txID] = outs
}
if !tx.IsCoinbase() {
for _, in := range tx.Vin {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return UTXO
}
// MineBlock 挖矿
func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {
var lastHash []byte
var lastHeight int
// 验证所有交易
for _, tx := range transactions {
if !bc.VerifyTransaction(tx) {
fmt.Printf("ERROR: Invalid transaction\n")
return nil
}
}
err := bc.db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("l"))
if err != nil {
return err
}
lastHash, err = item.ValueCopy(nil)
if err != nil {
return err
}
item, err = txn.Get(lastHash)
if err != nil {
return err
}
encodedBlock, err := item.ValueCopy(nil)
if err != nil {
return err
}
lastBlock, err := DeserializeBlock(encodedBlock)
if err != nil {
return err
}
lastHeight = lastBlock.Height
return nil
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return nil
}
newBlock := NewBlock(transactions, lastHash, lastHeight+1)
err = bc.db.Update(func(txn *badger.Txn) error {
encodedBlock, err := newBlock.Serialize()
if err != nil {
return err
}
err = txn.Set(newBlock.Hash, encodedBlock)
if err != nil {
return err
}
err = txn.Set([]byte("l"), newBlock.Hash)
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error mining block: %v\n", err)
return nil
}
return newBlock
}
// SignTransaction 签名交易
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
if err != nil {
fmt.Printf("Error finding transaction: %v\n", err)
return
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey, prevTXs)
}
// VerifyTransaction 验证交易
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
if tx.IsCoinbase() {
return true
}
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
if err != nil {
fmt.Printf("Error finding transaction: %v\n", err)
return false
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs)
}
// FindTransaction 查找交易
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
if bytes.Compare(tx.ID, ID) == 0 {
return *tx, nil
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return Transaction{}, fmt.Errorf("transaction not found")
}
// NewBlockchain 创建新区块链
func NewBlockchain() *Blockchain {
if !dbExists() {
fmt.Println("No existing blockchain found. Create one first.")
os.Exit(1)
}
var tip []byte
db, err := badger.Open(badger.DefaultOptions(dbPath))
if err != nil {
fmt.Printf("Error opening database: %v\n", err)
os.Exit(1)
}
err = db.Update(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("l"))
if err != nil {
return err
}
tip, err = item.ValueCopy(nil)
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
bc := Blockchain{tip, db}
return &bc
}
// CreateBlockchain 创建区块链
func CreateBlockchain(address string) *Blockchain {
if dbExists() {
fmt.Println("Blockchain already exists.")
os.Exit(1)
}
var tip []byte
db, err := badger.Open(badger.DefaultOptions(dbPath))
if err != nil {
fmt.Printf("Error opening database: %v\n", err)
os.Exit(1)
}
err = db.Update(func(txn *badger.Txn) error {
cbtx := NewCoinbaseTX(address, "")
genesis := GenesisBlock(cbtx)
encodedBlock, err := genesis.Serialize()
if err != nil {
return err
}
err = txn.Set(genesis.Hash, encodedBlock)
if err != nil {
return err
}
err = txn.Set([]byte("l"), genesis.Hash)
if err != nil {
return err
}
tip = genesis.Hash
return nil
})
if err != nil {
fmt.Printf("Error creating blockchain: %v\n", err)
os.Exit(1)
}
bc := Blockchain{tip, db}
return &bc
}
func dbExists() bool {
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
return false
}
return true
}
// TXOutputs 交易输出集合
type TXOutputs struct {
Outputs []TXOutput
}
UTXO 集合:加速交易查找
前面的 FindUTXO 方法每次都要遍历整个区块链,效率太低。我们可以维护一个 UTXO 集合来加速查找:
// blockchain/utxo.go
package blockchain
import (
"encoding/hex"
"fmt"
"github.com/dgraph-io/badger"
)
const utxoBucket = "chainstate"
// UTXOSet UTXO 集合
type UTXOSet struct {
Blockchain *Blockchain
}
// FindSpendableOutputs 查找可花费的输出
func (u *UTXOSet) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
accumulated := 0
db := u.Blockchain.db
err := db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
it := txn.NewIterator(opts)
defer it.Close()
prefix := []byte(utxoBucket)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
k := item.Key()
txIDHex := string(k[len(prefix):])
v, err := item.ValueCopy(nil)
if err != nil {
return err
}
outs := DeserializeOutputs(v)
for outIdx, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txIDHex] = append(unspentOutputs[txIDHex], outIdx)
}
}
}
return nil
})
if err != nil {
fmt.Printf("Error finding spendable outputs: %v\n", err)
}
return accumulated, unspentOutputs
}
// FindUTXO 根据公钥哈希查找 UTXO
func (u *UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput {
var UTXOs []TXOutput
db := u.Blockchain.db
err := db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
it := txn.NewIterator(opts)
defer it.Close()
prefix := []byte(utxoBucket)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
v, err := item.ValueCopy(nil)
if err != nil {
return err
}
outs := DeserializeOutputs(v)
for _, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}
return nil
})
if err != nil {
fmt.Printf("Error finding UTXO: %v\n", err)
}
return UTXOs
}
// CountTransactions 统计 UTXO 集中的交易数量
func (u *UTXOSet) CountTransactions() int {
count := 0
db := u.Blockchain.db
err := db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
it := txn.NewIterator(opts)
defer it.Close()
prefix := []byte(utxoBucket)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
count++
}
return nil
})
if err != nil {
fmt.Printf("Error counting transactions: %v\n", err)
}
return count
}
// Reindex 重建 UTXO 索引
func (u *UTXOSet) Reindex() {
db := u.Blockchain.db
// 删除旧的 UTXO 集
err := db.Update(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()
prefix := []byte(utxoBucket)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
k := item.KeyCopy(nil)
err := txn.Delete(k)
if err != nil {
return err
}
}
return nil
})
if err != nil {
fmt.Printf("Error deleting old UTXO set: %v\n", err)
return
}
// 重建
UTXO := u.Blockchain.FindUTXO()
err = db.Update(func(txn *badger.Txn) error {
for txID, outs := range UTXO {
key, err := hex.DecodeString(txID)
if err != nil {
return err
}
key = append([]byte(utxoBucket), key...)
encoded := outs.Serialize()
err = txn.Set(key, encoded)
if err != nil {
return err
}
}
return nil
})
if err != nil {
fmt.Printf("Error reindexing UTXO: %v\n", err)
}
}
// Update 在添加新区块后更新 UTXO 集
func (u *UTXOSet) Update(block *Block) {
db := u.Blockchain.db
err := db.Update(func(txn *badger.Txn) error {
for _, tx := range block.Transactions {
if !tx.IsCoinbase() {
for _, vin := range tx.Vin {
updatedOuts := TXOutputs{}
inTxID := hex.EncodeToString(vin.Txid)
key := append([]byte(utxoBucket), vin.Txid...)
item, err := txn.Get(key)
if err != nil {
continue
}
v, err := item.ValueCopy(nil)
if err != nil {
return err
}
outs := DeserializeOutputs(v)
for outIdx, out := range outs.Outputs {
if outIdx != vin.Vout {
updatedOuts.Outputs = append(updatedOuts.Outputs, out)
}
}
if len(updatedOuts.Outputs) == 0 {
err := txn.Delete(key)
if err != nil {
return err
}
} else {
err := txn.Set(key, updatedOuts.Serialize())
if err != nil {
return err
}
}
}
}
// 添加新的输出
newOutputs := TXOutputs{}
newOutputs.Outputs = append(newOutputs.Outputs, tx.Vout...)
txID := append([]byte(utxoBucket), tx.ID...)
err := txn.Set(txID, newOutputs.Serialize())
if err != nil {
return err
}
}
return nil
})
if err != nil {
fmt.Printf("Error updating UTXO set: %v\n", err)
}
}
P2P 网络:去中心化的关键
区块链的真正威力在于 P2P 网络。每个节点都是对等的,没有中心化的服务器。让我们实现一个简单的 P2P 网络层:
// network/server.go
package network
import (
"bytes"
"encoding/gob"
"encoding/hex"
"fmt"
"io"
"log"
"net"
"os"
"syscall"
"github.com/yourusername/go-blockchain/blockchain"
)
const (
protocol = "tcp"
version = 1
commandLength = 12
)
var (
nodeAddress string
miningAddress string
knownNodes = []string{"localhost:3000"} // 种子节点
blocksInTransit = [][]byte{}
mempool = make(map[string]blockchain.Transaction)
)
// 网络消息类型
type addr struct {
AddrList []string
}
type block struct {
AddrFrom string
Block []byte
}
type getblocks struct {
AddrFrom string
}
type getdata struct {
AddrFrom string
Type string
ID []byte
}
type inv struct {
AddrFrom string
Type string
Items [][]byte
}
type tx struct {
AddrFrom string
Transaction []byte
}
type verzion struct {
Version int
BestHeight int
AddrFrom string
}
// Server P2P 服务器
type Server struct {
nodeAddress string
miningAddress string
bc *blockchain.Blockchain
}
// NewServer 创建 P2P 服务器
func NewServer(nodeAddr, miningAddr string, bc *blockchain.Blockchain) *Server {
return &Server{
nodeAddress: nodeAddr,
miningAddress: miningAddr,
bc: bc,
}
}
// Start 启动服务器
func (s *Server) Start() {
nodeAddress = s.nodeAddress
miningAddress = s.miningAddress
ln, err := net.Listen(protocol, nodeAddress)
if err != nil {
log.Panic(err)
}
defer ln.Close()
// 如果不是种子节点,先向种子节点发送版本信息
if nodeAddress != knownNodes[0] {
sendVersion(knownNodes[0], s.bc)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Panic(err)
}
go s.handleConnection(conn)
}
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
request, err := io.ReadAll(conn)
if err != nil {
log.Panic(err)
}
command := bytesToCmd(request[:commandLength])
fmt.Printf("Received %s command\n", command)
switch command {
case "addr":
s.handleAddr(request)
case "block":
s.handleBlock(request)
case "inv":
s.handleInv(request)
case "getblocks":
s.handleGetBlocks(request)
case "getdata":
s.handleGetData(request)
case "tx":
s.handleTx(request)
case "version":
s.handleVersion(request)
default:
fmt.Println("Unknown command!")
}
}
func (s *Server) handleAddr(request []byte) {
var payload addr
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
knownNodes = append(knownNodes, payload.AddrList...)
fmt.Printf("There are %d known nodes now!\n", len(knownNodes))
// 请求区块
s.requestBlocks()
}
func (s *Server) handleBlock(request []byte) {
var payload block
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
blockData := payload.Block
block, err := blockchain.DeserializeBlock(blockData)
if err != nil {
log.Panic(err)
}
fmt.Println("Received a new block!")
s.bc.AddBlock([]*blockchain.Transaction{block.Transactions[0]}) // 简化
fmt.Printf("Added block %x\n", block.Hash)
if len(blocksInTransit) > 0 {
blockHash := blocksInTransit[0]
sendGetData(payload.AddrFrom, "block", blockHash)
blocksInTransit = blocksInTransit[1:]
} else {
// 重新索引 UTXO
// UTXOSet.Reindex()
}
}
func (s *Server) handleInv(request []byte) {
var payload inv
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
fmt.Printf("Received inventory with %d %s\n", len(payload.Items), payload.Type)
if payload.Type == "block" {
blocksInTransit = payload.Items
blockHash := payload.Items[0]
sendGetData(payload.AddrFrom, "block", blockHash)
newInTransit := [][]byte{}
for _, b := range blocksInTransit {
if !bytes.Equal(b, blockHash) {
newInTransit = append(newInTransit, b)
}
}
blocksInTransit = newInTransit
}
if payload.Type == "tx" {
txID := payload.Items[0]
if mempool[hex.EncodeToString(txID)].ID == nil {
sendGetData(payload.AddrFrom, "tx", txID)
}
}
}
func (s *Server) handleGetBlocks(request []byte) {
var payload getblocks
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
blocks := s.bc.GetBlockHashes()
sendInv(payload.AddrFrom, "block", blocks)
}
func (s *Server) handleGetData(request []byte) {
var payload getdata
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
if payload.Type == "block" {
block := s.bc.GetBlock([]byte(payload.ID))
if block == nil {
return
}
serialized, err := block.Serialize()
if err != nil {
log.Panic(err)
}
sendBlock(payload.AddrFrom, serialized)
}
if payload.Type == "tx" {
txID := hex.EncodeToString(payload.ID)
tx := mempool[txID]
sendTx(payload.AddrFrom, &tx)
}
}
func (s *Server) handleTx(request []byte) {
var payload tx
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
txData := payload.Transaction
transaction := blockchain.DeserializeTransaction(txData)
txID := hex.EncodeToString(transaction.ID)
mempool[txID] = transaction
// 如果是种子节点,广播给其他节点
if nodeAddress == knownNodes[0] {
for _, node := range knownNodes {
if node != nodeAddress && node != payload.AddrFrom {
sendInv(node, "tx", [][]byte{transaction.ID})
}
}
} else {
// 矿工节点:收集交易并挖矿
if len(mempool) >= 2 && len(miningAddress) > 0 {
MineTransactions:
var txs []*blockchain.Transaction
for id := range mempool {
tx := mempool[id]
if s.bc.VerifyTransaction(&tx) {
txs = append(txs, &tx)
}
}
if len(txs) == 0 {
fmt.Println("All transactions are invalid!")
break MineTransactions
}
// 创建 coinbase 交易
cbTx := blockchain.NewCoinbaseTX(miningAddress, "")
txs = append(txs, cbTx)
newBlock := s.bc.MineBlock(txs)
fmt.Println("New block is mined!")
// 清空 mempool
for _, tx := range txs {
txID := hex.EncodeToString(tx.ID)
delete(mempool, txID)
}
// 通知其他节点
for _, node := range knownNodes {
if node != nodeAddress {
sendInv(node, "block", [][]byte{newBlock.Hash})
}
}
}
}
}
func (s *Server) handleVersion(request []byte) {
var payload verzion
dataBytes := request[commandLength:]
err := gob.NewDecoder(bytes.NewReader(dataBytes)).Decode(&payload)
if err != nil {
log.Panic(err)
}
myBestHeight := s.bc.GetBestHeight()
foreignerBestHeight := payload.BestHeight
if myBestHeight < foreignerBestHeight {
s.requestBlocks()
} else if myBestHeight > foreignerBestHeight {
sendVersion(payload.AddrFrom, s.bc)
}
// 添加新节点
if !nodeIsKnown(payload.AddrFrom) {
knownNodes = append(knownNodes, payload.AddrFrom)
}
}
func (s *Server) requestBlocks() {
for _, node := range knownNodes {
sendGetBlocks(node)
}
}
// 网络通信辅助函数
func cmdToBytes(cmd string) []byte {
var bytes [commandLength]byte
for i, c := range cmd {
bytes[i] = byte(c)
}
return bytes[:]
}
func bytesToCmd(bytes []byte) string {
var cmd []byte
for _, b := range bytes {
if b != 0x0 {
cmd = append(cmd, b)
}
}
return string(cmd)
}
func sendAddr(address string) {
nodes := addr{knownNodes}
request := append(cmdToBytes("addr"), serializeData(nodes)...)
sendData(address, request)
}
func sendBlock(address string, blockData []byte) {
data := block{nodeAddress, blockData}
request := append(cmdToBytes("block"), serializeData(data)...)
sendData(address, request)
}
func sendGetData(address, kind string, id []byte) {
payload := getdata{nodeAddress, kind, id}
request := append(cmdToBytes("getdata"), serializeData(payload)...)
sendData(address, request)
}
func sendGetBlocks(address string) {
payload := getblocks{nodeAddress}
request := append(cmdToBytes("getblocks"), serializeData(payload)...)
sendData(address, request)
}
func sendInv(address, kind string, items [][]byte) {
inventory := inv{nodeAddress, kind, items}
request := append(cmdToBytes("inv"), serializeData(inventory)...)
sendData(address, request)
}
func sendTx(address string, tnx *blockchain.Transaction) {
data := tx{nodeAddress, tnx.Serialize()}
request := append(cmdToBytes("tx"), serializeData(data)...)
sendData(address, request)
}
func sendVersion(address string, bc *blockchain.Blockchain) {
bestHeight := bc.GetBestHeight()
payload := verzion{version, bestHeight, nodeAddress}
request := append(cmdToBytes("version"), serializeData(payload)...)
sendData(address, request)
}
func sendData(address string, data []byte) {
conn, err := net.Dial(protocol, address)
if err != nil {
fmt.Printf("%s is not available\n", address)
var updatedNodes []string
for _, node := range knownNodes {
if node != address {
updatedNodes = append(updatedNodes, node)
}
}
knownNodes = updatedNodes
return
}
defer conn.Close()
_, err = io.Copy(conn, bytes.NewReader(data))
if err != nil {
log.Panic(err)
}
}
func serializeData(data interface{}) []byte {
var buffer bytes.Buffer
gob.NewEncoder(&buffer).Encode(data)
return buffer.Bytes()
}
func nodeIsKnown(address string) bool {
for _, node := range knownNodes {
if node == address {
return true
}
}
return false
}
命令行接口
让我们用 cobra 为区块链创建一个命令行工具:
// cmd/cli.go
package cmd
import (
"fmt"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/yourusername/go-blockchain/blockchain"
"github.com/yourusername/go-blockchain/network"
"github.com/yourusername/go-blockchain/wallet"
)
// CLI 命令行接口
type CLI struct {
bc *blockchain.Blockchain
}
// Run 运行 CLI
func (cli *CLI) Run() {
rootCmd := &cobra.Command{
Use: "goblockchain",
Short: "A simple blockchain implementation in Go",
}
// 添加子命令
rootCmd.AddCommand(cli.addBlockCmd())
rootCmd.AddCommand(cli.createBlockchainCmd())
rootCmd.AddCommand(cli.getBalanceCmd())
rootCmd.AddCommand(cli.listAddressesCmd())
rootCmd.AddCommand(cli.printChainCmd())
rootCmd.AddCommand(cli.sendCmd())
rootCmd.AddCommand(cli.createWalletCmd())
rootCmd.AddCommand(cli.startNodeCmd())
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func (cli *CLI) createBlockchainCmd() *cobra.Command {
var address string
cmd := &cobra.Command{
Use: "createblockchain",
Short: "Create a new blockchain",
Run: func(cmd *cobra.Command, args []string) {
if !blockchain.ValidateAddress(address) {
fmt.Println("ERROR: Address is not valid")
return
}
bc := blockchain.CreateBlockchain(address)
defer bc.CloseDB()
fmt.Println("Done!")
},
}
cmd.Flags().StringVar(&address, "address", "", "The address to send genesis block reward to")
cmd.MarkFlagRequired("address")
return cmd
}
func (cli *CLI) getBalanceCmd() *cobra.Command {
var address string
cmd := &cobra.Command{
Use: "getbalance",
Short: "Get balance of an address",
Run: func(cmd *cobra.Command, args []string) {
if !blockchain.ValidateAddress(address) {
fmt.Println("ERROR: Address is not valid")
return
}
bc := blockchain.NewBlockchain()
defer bc.CloseDB()
utxoSet := blockchain.UTXOSet{bc}
pubKeyHash := blockchain.Base58Decode([]byte(address))
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
UTXOs := utxoSet.FindUTXO(pubKeyHash)
balance := 0
for _, out := range UTXOs {
balance += out.Value
}
fmt.Printf("Balance of '%s': %d\n", address, balance)
},
}
cmd.Flags().StringVar(&address, "address", "", "The address to get balance for")
cmd.MarkFlagRequired("address")
return cmd
}
func (cli *CLI) sendCmd() *cobra.Command {
var from, to string
var amount int
cmd := &cobra.Command{
Use: "send",
Short: "Send coins from one address to another",
Run: func(cmd *cobra.Command, args []string) {
if !blockchain.ValidateAddress(from) {
fmt.Println("ERROR: Sender address is not valid")
return
}
if !blockchain.ValidateAddress(to) {
fmt.Println("ERROR: Recipient address is not valid")
return
}
bc := blockchain.NewBlockchain()
defer bc.CloseDB()
wallets, err := wallet.NewWallets()
if err != nil {
fmt.Printf("Error loading wallets: %v\n", err)
return
}
w := wallets.GetWallet(from)
if w == nil {
fmt.Println("ERROR: Sender wallet not found")
return
}
utxoSet := blockchain.UTXOSet{bc}
tx := blockchain.NewUTXOTransaction(w, to, amount, &utxoSet)
if tx == nil {
fmt.Println("ERROR: Transaction creation failed")
return
}
bc.MineBlock([]*blockchain.Transaction{tx})
fmt.Println("Success!")
utxoSet.Update(bc.GetLatestBlock())
},
}
cmd.Flags().StringVar(&from, "from", "", "Source wallet address")
cmd.Flags().StringVar(&to, "to", "", "Destination wallet address")
cmd.Flags().IntVar(&amount, "amount", 0, "Amount of coins to send")
cmd.MarkFlagRequired("from")
cmd.MarkFlagRequired("to")
cmd.MarkFlagRequired("amount")
return cmd
}
func (cli *CLI) createWalletCmd() *cobra.Command {
return &cobra.Command{
Use: "createwallet",
Short: "Create a new wallet",
Run: func(cmd *cobra.Command, args []string) {
wallets, err := wallet.NewWallets()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
address := wallets.CreateWallet()
wallets.SaveToFile()
fmt.Printf("Your new address: %s\n", address)
},
}
}
func (cli *CLI) printChainCmd() *cobra.Command {
return &cobra.Command{
Use: "printchain",
Short: "Print all blocks in the blockchain",
Run: func(cmd *cobra.Command, args []string) {
bc := blockchain.NewBlockchain()
defer bc.CloseDB()
bci := bc.Iterator()
for {
block := bci.Next()
if block == nil {
break
}
fmt.Printf("============ Block %x ============\n", block.Hash)
fmt.Printf("Height: %d\n", block.Height)
fmt.Printf("Prev. block: %x\n", block.PrevBlockHash)
pow := blockchain.NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
fmt.Println()
for _, tx := range block.Transactions {
fmt.Println(tx)
}
fmt.Println()
if len(block.PrevBlockHash) == 0 {
break
}
}
},
}
}
func (cli *CLI) startNodeCmd() *cobra.Command {
var nodeAddr, minerAddr string
cmd := &cobra.Command{
Use: "startnode",
Short: "Start a node in the P2P network",
Run: func(cmd *cobra.Command, args []string) {
if nodeAddr == "" {
fmt.Println("Node address cannot be empty")
return
}
bc := blockchain.NewBlockchain()
defer bc.CloseDB()
server := network.NewServer(nodeAddr, minerAddr, bc)
fmt.Printf("Starting node on %s\n", nodeAddr)
server.Start()
},
}
cmd.Flags().StringVar(&nodeAddr, "node", "", "Node address (e.g. localhost:3000)")
cmd.Flags().StringVar(&minerAddr, "miner", "", "Miner address for block rewards")
cmd.MarkFlagRequired("node")
return cmd
}
与以太坊交互:go-ethereum
除了从零构建区块链,Go 还有一个非常重要的应用:通过 go-ethereum 库与以太坊区块链交互。
安装与连接
go get github.com/ethereum/go-ethereum
// ethereum/client.go
package ethereum
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
// EthClient 以太坊客户端封装
type EthClient struct {
client *ethclient.Client
}
// NewEthClient 创建以太坊客户端
func NewEthClient(rpcURL string) (*EthClient, error) {
client, err := ethclient.Dial(rpcURL)
if err != nil {
return nil, fmt.Errorf("failed to connect to ethereum node: %v", err)
}
return &EthClient{client: client}, nil
}
// Close 关闭连接
func (ec *EthClient) Close() {
ec.client.Close()
}
// GetBalance 查询 ETH 余额
func (ec *EthClient) GetBalance(address string) (*big.Float, error) {
account := common.HexToAddress(address)
balance, err := ec.client.BalanceAt(context.Background(), account, nil)
if err != nil {
return nil, err
}
// 将 Wei 转换为 ETH
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(1e18))
return ethValue, nil
}
// GetBlockNumber 获取最新区块号
func (ec *EthClient) GetBlockNumber() (uint64, error) {
header, err := ec.client.HeaderByNumber(context.Background(), nil)
if err != nil {
return 0, err
}
return header.Number.Uint64(), nil
}
// GetBlockByNumber 根据区块号获取区块信息
func (ec *EthClient) GetBlockByNumber(number int64) (*types.Block, error) {
blockNum := big.NewInt(number)
block, err := ec.client.BlockByNumber(context.Background(), blockNum)
if err != nil {
return nil, err
}
return block, nil
}
// GetTransactionCount 获取交易 nonce
func (ec *EthClient) GetTransactionCount(address string) (uint64, error) {
account := common.HexToAddress(address)
nonce, err := ec.client.PendingNonceAt(context.Background(), account)
if err != nil {
return 0, err
}
return nonce, nil
}
// SuggestGasPrice 获取建议的 Gas 价格
func (ec *EthClient) SuggestGasPrice() (*big.Int, error) {
return ec.client.SuggestGasPrice(context.Background())
}
发送 ETH 交易
// ethereum/transfer.go
package ethereum
import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// TransferETH 发送 ETH
func (ec *EthClient) TransferETH(privateKeyHex string, toAddress string, amountETH float64) (string, error) {
// 解析私钥
privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil {
return "", fmt.Errorf("invalid private key: %v", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return "", fmt.Errorf("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
// 获取 nonce
nonce, err := ec.client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return "", fmt.Errorf("failed to get nonce: %v", err)
}
// 转换金额为 Wei
value := big.NewInt(int64(amountETH * 1e18))
// 获取 Gas 价格
gasPrice, err := ec.client.SuggestGasPrice(context.Background())
if err != nil {
return "", fmt.Errorf("failed to get gas price: %v", err)
}
// 设置 Gas 限制(普通 ETH 转账 21000)
gasLimit := uint64(21000)
// 获取链 ID
chainID, err := ec.client.ChainID(context.Background())
if err != nil {
return "", fmt.Errorf("failed to get chain ID: %v", err)
}
// 创建交易
toAddr := common.HexToAddress(toAddress)
tx := types.NewTransaction(nonce, toAddr, value, gasLimit, gasPrice, nil)
// 签名交易
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
return "", fmt.Errorf("failed to sign transaction: %v", err)
}
// 发送交易
err = ec.client.SendTransaction(context.Background(), signedTx)
if err != nil {
return "", fmt.Errorf("failed to send transaction: %v", err)
}
return signedTx.Hash().Hex(), nil
}
与智能合约交互
// ethereum/contract.go
package ethereum
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
// ERC20Balance 查询 ERC20 代币余额
func (ec *EthClient) ERC20Balance(tokenAddress, walletAddress string) (*big.Int, error) {
// ERC20 balanceOf(address) 函数签名
tokenAddr := common.HexToAddress(tokenAddress)
walletAddr := common.HexToAddress(walletAddress)
// 构建函数调用数据
// balanceOf 的函数选择器:0x70a08231
parsedABI, err := abi.JSON(strings.NewReader(`[
{
"constant": true,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function"
}
]`))
if err != nil {
return nil, fmt.Errorf("failed to parse ABI: %v", err)
}
callData, err := parsedABI.Pack("balanceOf", walletAddr)
if err != nil {
return nil, fmt.Errorf("failed to pack call data: %v", err)
}
// 执行调用
msg := ethereum.CallMsg{
To: &tokenAddr,
Data: callData,
}
result, err := ec.client.CallContract(context.Background(), msg, nil)
if err != nil {
return nil, fmt.Errorf("failed to call contract: %v", err)
}
// 解析返回值
var balance *big.Int
err = parsedABI.UnpackIntoInterface(&balance, "balanceOf", result)
if err != nil {
return nil, fmt.Errorf("failed to unpack result: %v", err)
}
return balance, nil
}
// WatchEvents 监听合约事件
func (ec *EthClient) WatchEvents(contractAddress string, topicHash common.Hash) {
contractAddr := common.HexToAddress(contractAddress)
query := ethereum.FilterQuery{
Addresses: []common.Address{contractAddr},
Topics: [][]common.Hash{{topicHash}},
}
logs := make(chan types.Log)
sub, err := ec.client.SubscribeFilterLogs(context.Background(), query, logs)
if err != nil {
fmt.Printf("Failed to subscribe: %v\n", err)
return
}
fmt.Println("Watching for events...")
for {
select {
case err := <-sub.Err():
fmt.Printf("Subscription error: %v\n", err)
return
case vLog := <-logs:
fmt.Printf("New event at block %d: %x\n", vLog.BlockNumber, vLog.TxHash)
}
}
}
使用示例
// main.go
package main
import (
"fmt"
"log"
)
func main() {
// 连接到以太坊节点(可以使用 Infura 或 Alchemy)
client, err := NewEthClient("https://mainnet.infura.io/v3/YOUR_PROJECT_ID")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// 查询余额
balance, err := client.GetBalance("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Balance: %s ETH\n", balance.Text('f', 6))
// 获取最新区块号
blockNum, err := client.GetBlockNumber()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Latest block: %d\n", blockNum)
// 获取建议 Gas 价格
gasPrice, err := client.SuggestGasPrice()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Suggested gas price: %s Gwei\n",
new(big.Float).Quo(
new(big.Float).SetInt(gasPrice),
new(big.Float).SetInt64(1e9),
).Text('f', 2))
// 查询 ERC20 代币余额(以 USDT 为例)
tokenBalance, err := client.ERC20Balance(
"0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("USDT Balance: %s\n", tokenBalance.String())
}
智能合约概述
虽然智能合约本身使用 Solidity 编写,但 Go 可以用来部署和交互。go-ethereum 提供了 abigen 工具来生成合约绑定:
# 安装 abigen
go install github.com/ethereum/go-ethereum/cmd/abigen@latest
# 从 ABI 生成 Go 绑定
abigen --abi=contract.abi --pkg=contract --out=contract.go
# 从 Solidity 直接生成
abigen --sol=Token.sol --pkg=token --out=token.go
生成的绑定可以这样使用:
// deploy.go
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"your-project/token"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("your-private-key")
if err != nil {
log.Fatal(err)
}
chainID, err := client.ChainID(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
log.Fatal(err)
}
// 部署合约
initialSupply := big.NewInt(1000000)
address, tx, instance, err := token.DeployToken(auth, client, initialSupply)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Contract deployed at: %s\n", address.Hex())
fmt.Printf("Transaction hash: %s\n", tx.Hash().Hex())
// 调用合约方法
balance, err := instance.BalanceOf(nil, address)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Token balance: %s\n", balance.String())
}
测试区块链
编写单元测试来验证我们的实现:
// blockchain/blockchain_test.go
package blockchain
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProofOfWork(t *testing.T) {
block := &Block{
Timestamp: 1234567890,
Transactions: []*Transaction{},
PrevBlockHash: []byte{},
Height: 0,
}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
assert.NotEmpty(t, hash)
assert.True(t, pow.Validate())
assert.Equal(t, nonce, block.Nonce)
}
func TestNewBlock(t *testing.T) {
cbTx := NewCoinbaseTX("test_address", "test data")
genesis := GenesisBlock(cbTx)
assert.NotNil(t, genesis)
assert.Equal(t, 0, genesis.Height)
assert.Empty(t, genesis.PrevBlockHash)
assert.NotEmpty(t, genesis.Hash)
}
func TestTransactionHash(t *testing.T) {
tx := &Transaction{
Vin: []TXInput{{Txid: []byte{}, Vout: -1, Signature: nil, PubKey: []byte("test")}},
Vout: []TXOutput{{Value: 10, PubKeyHash: []byte("test")}},
}
tx.SetID()
assert.NotEmpty(t, tx.ID)
assert.True(t, tx.IsCoinbase())
}
func TestWallet(t *testing.T) {
w := NewWallet()
require.NotNil(t, w)
address := w.GetAddress()
assert.NotEmpty(t, address)
assert.True(t, ValidateAddress(string(address)))
}
func TestWalletDifferentAddresses(t *testing.T) {
w1 := NewWallet()
w2 := NewWallet()
// 两个钱包的地址应该不同
assert.NotEqual(t, string(w1.GetAddress()), string(w2.GetAddress()))
}
func TestBlockSerialization(t *testing.T) {
block := &Block{
Timestamp: 1234567890,
PrevBlockHash: []byte("prev_hash"),
Hash: []byte("block_hash"),
Nonce: 42,
Height: 10,
Transactions: []*Transaction{},
}
serialized, err := block.Serialize()
require.NoError(t, err)
require.NotEmpty(t, serialized)
deserialized, err := DeserializeBlock(serialized)
require.NoError(t, err)
assert.Equal(t, block.Timestamp, deserialized.Timestamp)
assert.Equal(t, block.Nonce, deserialized.Nonce)
assert.Equal(t, block.Height, deserialized.Height)
}
区块链安全注意事项
在构建区块链系统时,安全是重中之重:
- 私钥保护:永远不要在代码中硬编码私钥,使用环境变量或密钥管理服务
- 交易验证:严格验证每笔交易的签名和余额
- 网络防护:防止 Sybil 攻击、DDoS 攻击
- 重入攻击:智能合约中要特别注意状态变更顺序
- 整数溢出:所有金额计算都要检查溢出
// 安全示例:验证金额不溢出
func SafeAdd(a, b int) (int, error) {
if a > 0 && b > math.MaxInt-a {
return 0, fmt.Errorf("integer overflow")
}
if a < 0 && b < math.MinInt-a {
return 0, fmt.Errorf("integer underflow")
}
return a + b, nil
}
总结
恭喜你完成了这趟区块链之旅!我们从零开始,用 Go 实现了一个包含以下组件的完整区块链系统:
- 区块与哈希:区块链的基本数据结构,通过加密哈希链接
- 工作量证明(PoW):通过计算难题来保护网络安全
- 交易系统:UTXO 模型,支持输入、输出和数字签名
- 钱包:ECDSA 密钥对管理和地址生成
- P2P 网络:去中心化的节点通信和区块同步
- UTXO 集合:加速交易查找和余额计算
- CLI 工具:命令行操作区块链
- 以太坊交互:通过 go-ethereum 与真实的区块链网络交互
Go 是区块链开发的绝佳语言,原因包括:
- 并发支持:goroutine 天然适合 P2P 网络
- 标准库丰富:加密、网络、序列化一应俱全
- 编译为单一二进制:部署简单,节点启动快
- 类型安全:在金融系统中至关重要
如果你想继续深入,推荐以下方向:
- 阅读 go-ethereum 源码,学习生产级区块链实现
- 研究不同的共识机制(PoS、DPoS、BFT)
- 学习 Solidity,开发智能合约
- 探索 Layer2 方案(Rollup、State Channel)
下一篇文章,我们将实战一个完整的 Go Web 全栈项目,敬请期待!
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。