Go 与区块链:从概念到实现

深入理解区块链核心概念,使用 Go 实现简单的区块链系统,包括工作量证明、加密哈希、交易结构、钱包和 P2P 网络

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)
}

区块链安全注意事项

在构建区块链系统时,安全是重中之重:

  1. 私钥保护:永远不要在代码中硬编码私钥,使用环境变量或密钥管理服务
  2. 交易验证:严格验证每笔交易的签名和余额
  3. 网络防护:防止 Sybil 攻击、DDoS 攻击
  4. 重入攻击:智能合约中要特别注意状态变更顺序
  5. 整数溢出:所有金额计算都要检查溢出
// 安全示例:验证金额不溢出
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 实现了一个包含以下组件的完整区块链系统:

  1. 区块与哈希:区块链的基本数据结构,通过加密哈希链接
  2. 工作量证明(PoW):通过计算难题来保护网络安全
  3. 交易系统:UTXO 模型,支持输入、输出和数字签名
  4. 钱包:ECDSA 密钥对管理和地址生成
  5. P2P 网络:去中心化的节点通信和区块同步
  6. UTXO 集合:加速交易查找和余额计算
  7. CLI 工具:命令行操作区块链
  8. 以太坊交互:通过 go-ethereum 与真实的区块链网络交互

Go 是区块链开发的绝佳语言,原因包括:

  • 并发支持:goroutine 天然适合 P2P 网络
  • 标准库丰富:加密、网络、序列化一应俱全
  • 编译为单一二进制:部署简单,节点启动快
  • 类型安全:在金融系统中至关重要

如果你想继续深入,推荐以下方向:

  • 阅读 go-ethereum 源码,学习生产级区块链实现
  • 研究不同的共识机制(PoS、DPoS、BFT)
  • 学习 Solidity,开发智能合约
  • 探索 Layer2 方案(Rollup、State Channel)

下一篇文章,我们将实战一个完整的 Go Web 全栈项目,敬请期待!

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页