Go语言实现区块链——添加coinbase交易及UTXO

avatar
作者
猴君
阅读量:0

        在本篇博客中,我们将深入探讨一个用Go语言编写的简易区块链实现。通过这个示例,我们可以理解区块链背后的核心概念,包括如何创建和管理区块、如何处理交易以及如何通过工作量证明算法保证区块链的安全性。我们还会探讨如何使用BoltDB这个轻量级的键值数据库来持久化存储区块链数据。在这个过程中,我们将一步一步构建起区块链的基本结构,并演示如何通过命令行界面(CLI)与区块链进行交互。

建议观看顺序:

Go语言实现简单区块链-CSDN博客

Go语言实现简单区块链——增加POW机制-CSDN博客

1.block.go

区块的序列化与反序列化

为了能够在网络中传输或在磁盘上存储,我们需要将区块序列化和反序列化。序列化是将区块转换为字节序列的过程,而反序列化是将字节序列还原为原始区块的过程。

  • 序列化 (Serialize 方法): 使用Go的encoding/gob包来进行序列化操作。这个方法将Block结构体编码成字节流,以便于存储或网络传输。

  • 反序列化 (DeserializeBlock 函数): 与序列化相反,这个函数将字节流解码回Block结构体。

区块的创建

  • 新建区块 (NewBlock 函数): 当需要添加新的区块时,我们会调用这个函数。该函数接受交易列表和前一个区块的哈希值作为参数,创建一个新的区块。在这个过程中,我们还会进行工作量证明的计算。

  • 创世区块 (NewGenesisBlock 函数): 创世区块是区块链中的第一个区块。这个函数通过调用NewBlock函数并传递一个特殊的coinbase交易来创建创世区块。

// -*- coding: utf-8 -*- // Time    : 2024/4/15 22:15 // Author  : blue // File    : block.go // Software: Goland package main  import ( 	"bytes" 	"crypto/sha256" 	"encoding/gob" 	"log" 	"time" )  // Block keeps block headers type Block struct { 	Timestamp     int64          // 时间戳 	Transactions  []*Transaction // 交易 	PrevBlockHash []byte         // 前一个区块的哈希 	Hash          []byte         // 当前区块的哈希 	Nonce         int            // 随机数 }  // Serializes 是将区块序列化为字节数组 func (b *Block) Serialize() []byte { 	var result bytes.Buffer 	encoder := gob.NewEncoder(&result)  	err := encoder.Encode(b) 	if err != nil { 		log.Panic(err) 	}  	return result.Bytes() }  // HashTransactions将区块中的所有交易序列化并返回一个哈希值 func (b *Block) HashTransactions() []byte { 	var txHashes [][]byte 	var txHash [32]byte // 遍历区块中的所有交易 	for _, tx := range b.Transactions { 		txHashes = append(txHashes, tx.ID) 	} 	txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))  	return txHash[:] }  // NewBlock 将创建一个新的区块 func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { 	// 创建区块 	block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} 	// 创建工作量证明 	pow := NewProofOfWork(block) 	// 运行工作量证明 	nonce, hash := pow.Run()  	block.Hash = hash[:] 	block.Nonce = nonce  	return block }  // NewGenesisBlock 将创建并返回创世区块 func NewGenesisBlock(coinbase *Transaction) *Block { 	return NewBlock([]*Transaction{coinbase}, []byte{}) }  // DeserializeBlock 将区块的字节数组反序列化为区块 func DeserializeBlock(d []byte) *Block { 	var block Block  	decoder := gob.NewDecoder(bytes.NewReader(d)) 	err := decoder.Decode(&block) 	if err != nil { 		log.Panic(err) 	}  	return &block } 

 2.blockchains.go

 这段代码实现了区块链的核心数据结构和功能,包括区块链、区块迭代器以及创建、查询和添加区块等操作。让我们深入分析:

数据结构

  • Blockchain: 区块链结构体,包含最新区块的哈希值和数据库指针。
  • BlockchainIterator: 区块链迭代器,用于遍历区块链,包含当前区块的哈希值和数据库指针。

函数解析

  • MineBlock: 挖矿函数,根据交易列表创建新区块,并将其添加到区块链中。
    • 首先获取最新区块的哈希值。
    • 然后创建新的区块,并进行工作量证明计算。
    • 最后将新区块序列化并存储到数据库中,更新区块链的最新区块哈希值。
  • FindUnspentTransactions: 查找指定地址的未花费交易。
    • 遍历区块链,检查每个交易的输出是否属于指定地址,以及是否已被花费。
    • 将未花费的交易添加到结果列表中。
  • FindUTXO: 查找指定地址的未花费交易输出 (UTXO)。
    • 调用 FindUnspentTransactions 获取未花费交易。
    • 遍历未花费交易的输出,将属于指定地址的输出添加到结果列表中。
  • FindSpendableOutputs: 查找满足指定金额的未花费交易输出。
    • 遍历未花费交易的输出,累加金额,直到满足指定金额。
    • 返回累加金额和满足条件的未花费交易输出。
  • Iterator: 创建区块链迭代器,从最新区块开始遍历区块链。
  • Next: 获取区块链迭代器指向的当前区块,并将迭代器指向下一个区块。
  • dbExists: 检查区块链数据库文件是否存在。
  • NewBlockchain: 打开区块链数据库,并创建区块链对象。
  • CreateBlockchain: 创建新的区块链,包含创世区块。
    • 首先创建 coinbase 交易,奖励给指定地址。
    • 然后创建创世区块,包含 coinbase 交易。
    • 最后将创世区块存储到数据库中,并创建区块链对象。
  • AddBlock: 添加新区块到区块链。
    • 首先创建 coinbase 交易,奖励给指定地址。
    • 然后调用MineBlock挖矿并添加新区块到区块链中。
// -*- coding: utf-8 -*- // Time    : 2024/4/15 22:15 // Author  : blue // File    : blockchains.go // Software: Goland package main  import ( 	"encoding/hex" 	"fmt" 	"github.com/boltdb/bolt" 	"log" 	"os" )  const dbFile = "blockchain.db"                                                                      //数据库文件 const blocksBucket = "blocks"                                                                       //存储区块的桶 const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" //创世块的交易数据  // Blockchain 包含一个区块链 type Blockchain struct { 	tip []byte   //最新区块的哈希 	db  *bolt.DB //数据库指针 }  // BlockchainIterator 将用于迭代区块链 type BlockchainIterator struct { 	currentHash []byte   //当前区块的哈希 	db          *bolt.DB //数据库指针 }  // MineBlock 将用于挖掘新块 func (bc *Blockchain) MineBlock(transactions []*Transaction) { 	var lastHash []byte //记录最新区块的哈希 	//获取最新区块的哈希 	err := bc.db.View(func(tx *bolt.Tx) error { 		//获取区块桶 		b := tx.Bucket([]byte(blocksBucket)) 		//获取最新区块的哈希 		lastHash = b.Get([]byte("l")) 		return nil 	})  	if err != nil { 		log.Panic(err) 	} 	//创建新区块(包含验证) 	newBlock := NewBlock(transactions, lastHash) 	//将新区块存储到数据库中 	err = bc.db.Update(func(tx *bolt.Tx) error { 		//获取区块桶 		b := tx.Bucket([]byte(blocksBucket)) 		//将新区块存储到数据库中 		err := b.Put(newBlock.Hash, newBlock.Serialize()) 		if err != nil { 			log.Panic(err) 		} 		//将最新区块的哈希存储到数据库中 		err = b.Put([]byte("l"), newBlock.Hash) 		if err != nil { 			log.Panic(err) 		} 		//更新区块链的tip 		bc.tip = newBlock.Hash  		return nil 	}) }  // FindUnspentTransactions 返回未花费的交易 func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { 	//未花费的交易 	var unspentTXs []Transaction 	//已花费的输出 	spentTXOs := make(map[string][]int) 	//迭代区块链 	bci := bc.Iterator() 	for { 		//获取下一个区块 		block := bci.Next() 		//遍历区块中的交易 		for _, tx := range block.Transactions { 			//将交易ID转换为字符串 			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.CanBeUnlockedWith(address) { 					unspentTXs = append(unspentTXs, *tx) 				} 			} 			//如果交易不是coinbase交易,则遍历交易的输入 			if tx.IsCoinbase() == false { 				//遍历交易的输入 				for _, in := range tx.Vin { 					//如果输入可以解锁,则将输出添加到已花费的输出中 					if in.CanUnlockOutputWith(address) { 						//将交易ID转换为字符串 						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(address string) []TXOutput { 	var UTXOs []TXOutput 	//未花费的交易 	unspentTransactions := bc.FindUnspentTransactions(address) 	//遍历未花费的交易 	for _, tx := range unspentTransactions { 		//遍历交易的输出 		for _, out := range tx.Vout { 			//如果输出可以被解锁,则将输出添加到未花费的输出中 			if out.CanBeUnlockedWith(address) { 				UTXOs = append(UTXOs, out) 			} 		} 	}  	return UTXOs }  // FindSpendableOutputs 返回足够的未花费输出以满足要求的金额 func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { 	//未花费的输出 	unspentOutputs := make(map[string][]int) 	//未花费的交易 	unspentTXs := bc.FindUnspentTransactions(address) 	//累计金额 	accumulated := 0  Work: 	//遍历未花费的交易 	for _, tx := range unspentTXs { 		//将交易ID转换为字符串 		txID := hex.EncodeToString(tx.ID) 		//遍历交易的输出 		for outIdx, out := range tx.Vout { 			//如果输出可以被解锁且累计金额小于要求的金额,则将输出添加到未花费的输出中 			if out.CanBeUnlockedWith(address) && accumulated < amount { 				//将输出添加到未花费的输出中 				accumulated += out.Value 				//将输出的索引添加到未花费的输出中 				unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) 				//如果累计金额大于等于要求的金额,则退出循环 				if accumulated >= amount { 					break Work 				} 			} 		} 	} 	//返回累计金额和未花费的输出 	return accumulated, unspentOutputs }  // Iterator 返回一个迭代器  func (bc *Blockchain) Iterator() *BlockchainIterator { 	//迭代器对象是一个指向区块链的指针和一个指向数据库的指针 	bci := &BlockchainIterator{bc.tip, bc.db} 	return bci }  // Next 返回区块链中的下一个区块 func (i *BlockchainIterator) Next() *Block { 	var block *Block 	//获取当前区块 	err := i.db.View(func(tx *bolt.Tx) error { 		//获取区块桶 		b := tx.Bucket([]byte(blocksBucket)) 		//获取当前区块 		encodedBlock := b.Get(i.currentHash) 		//反序列化区块 		block = DeserializeBlock(encodedBlock)  		return nil 	})  	if err != nil { 		log.Panic(err) 	} 	//更新当前区块的哈希 	i.currentHash = block.PrevBlockHash  	return block }  // dbExists 检查数据库是否存在 func dbExists() bool {  	if _, err := os.Stat(dbFile); os.IsNotExist(err) { 		return false 	}  	return true }  // NewBlockchain 创建一个新的区块 func NewBlockchain(address string) *Blockchain { 	//检查数据库是否存在 	if dbExists() == false { 		fmt.Println("No existing blockchain found. Create one first.") 		os.Exit(1) 	} 	//存储最新区块的哈希 	var tip []byte 	db, err := bolt.Open(dbFile, 0600, nil) 	if err != nil { 		log.Panic(err) 	} 	//更新数据库 	err = db.Update(func(tx *bolt.Tx) error {  		b := tx.Bucket([]byte(blocksBucket)) 		tip = b.Get([]byte("l"))  		return nil 	})  	if err != nil { 		log.Panic(err) 	} 	//创建一个区块链对象 	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 := bolt.Open(dbFile, 0600, nil) 	if err != nil { 		log.Panic(err) 	} 	//更新数据库 	err = db.Update(func(tx *bolt.Tx) error { 		//创建一个新的coinbase交易 		cbtx := NewCoinbaseTX(address, genesisCoinbaseData) 		//创建一个新的区块 		genesis := NewGenesisBlock(cbtx) 		//创建一个新的桶 		b, err := tx.CreateBucket([]byte(blocksBucket)) 		if err != nil { 			log.Panic(err) 		} 		//将区块存储到数据库中 		err = b.Put(genesis.Hash, genesis.Serialize()) 		if err != nil { 			log.Panic(err) 		}  		err = b.Put([]byte("l"), genesis.Hash) 		if err != nil { 			log.Panic(err) 		} 		tip = genesis.Hash  		return nil 	})  	if err != nil { 		log.Panic(err) 	}  	bc := Blockchain{tip, db}  	return &bc }  // AddBlock 将用于添加区块到区块链 func AddBlock(address string) { 	//创建一个新的区块 	bc := NewBlockchain(address) 	defer bc.db.Close() 	//创建一个新的coinbase交易 	//cbtx := NewCoinbaseTX(address, "") 	//挖掘新块并存放到数据库中 	bc.MineBlock([]*Transaction{NewCoinbaseTX(address, "")}) 	//往address地址发送奖励 	fmt.Println("Coinbase交易完成,以奖励的方式将硬币发送到地址:", address) 	fmt.Println("Success!") } 

 3.proofwork.go

这段代码实现了区块链中的工作量证明 (Proof of Work, PoW) 机制,用于确保区块链的安全性,防止恶意攻击。让我们深入理解其原理和实现:

工作量证明概述

PoW 是一种共识机制,要求节点进行一定的计算工作才能将新区块添加到区块链中。这种机制通过消耗计算资源来增加攻击成本,从而保证区块链的安全性。

代码解析

  • ProofOfWork 结构体:

    • block: 指向区块的指针,包含区块头信息。
    • target: 指向一个大整数的指针,代表挖矿难度目标。哈希值必须小于该目标才算有效。
  • NewProofOfWork:

    • 创建一个新的 ProofOfWork 对象,根据难度目标设置 target 值。
    • 难度目标通过 targetBits 常量控制,值越小,难度越大。
  • prepareData:

    • 准备用于哈希运算的数据,包括区块头信息、随机数 nonce 等。
    • 将数据拼接成字节数组,方便进行哈希运算。
  • Run:

    • 执行挖矿过程,不断尝试不同的 nonce 值,直到找到满足难度目标的哈希值。
    • 循环计算哈希值,并与 target 进行比较。
    • 如果哈希值小于 target,则挖矿成功,返回 nonce 和哈希值。
    • 否则,继续尝试下一个 nonce 值。
  • Validate:

    • 验证区块的工作量证明是否有效。
    • 根据区块头信息和 nonce 计算哈希值,并与 target 比较。
    • 如果哈希值小于 target,则验证通过,返回 true
    • 否则,验证失败,返回 false
// -*- coding: utf-8 -*- // Time    : 2024/4/14 23:48 // Author  : blue // File    : proofwork.go // Software: Goland package main  import ( 	"bytes" 	"crypto/sha256" 	"fmt" 	"math" 	"math/big" )  var ( 	maxNonce = math.MaxInt64 )  const targetBits = 16  // ProofOfWork 包含一个指向区块的指针和一个指向big.Int的指针 type ProofOfWork struct { 	block  *Block 	target *big.Int }  // NewProofOfWork 创建并返回一个ProofOfWork结构 func NewProofOfWork(b *Block) *ProofOfWork { 	target := big.NewInt(1) 	target.Lsh(target, uint(256-targetBits))  	pow := &ProofOfWork{b, target}  	return pow }  // 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 a new block") 	for nonce < maxNonce { 		data := pow.prepareData(nonce)  		hash = sha256.Sum256(data) 		fmt.Printf("\r%x", hash) 		hashInt.SetBytes(hash[:])  		if hashInt.Cmp(pow.target) == -1 { 			break 		} else { 			nonce++ 		} 	} 	fmt.Print("\n\n")  	return nonce, hash[:] }  // Validate 验证工作量证明 func (pow *ProofOfWork) Validate() bool { 	var hashInt big.Int  	data := pow.prepareData(pow.block.Nonce) 	hash := sha256.Sum256(data) 	hashInt.SetBytes(hash[:])  	isValid := hashInt.Cmp(pow.target) == -1  	return isValid } 

4.Transaction.go

这段代码定义了区块链中的交易数据结构,包括交易输入、交易输出和交易本身,以及创建 coinbase 交易和普通交易的函数。让我们深入分析:

数据结构

  • Transaction: 交易结构体,包含交易 ID、输入列表和输出列表。
  • TXInput: 交易输入结构体,包含引用的交易 ID、输出索引和解锁脚本。
  • TXOutput: 交易输出结构体,包含金额和锁定脚本。

拓展:

        在区块链技术,特别是比特币及其衍生加密货币中,交易的验证过程涉及到两个重要的概念:锁定脚本(ScriptPubKey)和解锁脚本(ScriptSig)。这两个脚本共同工作,确保只有资产的合法拥有者才能花费这些资产。在本文Transaction.go代码示例中,为了便于理解和实现,锁定脚本和解锁脚本简化为货币持有者的地址。

锁定脚本(ScriptPubKey)
// TXInput 		表示交易输入 type TXInput struct { 	Txid      []byte //引用的交易ID,表示交易的哈希 	Vout      int    //引用的输出索引 	ScriptSig string //解锁脚本 }

        在TXOutput结构体中,ScriptPubKey字段代表锁定脚本。它的作用是指定谁有权利花费这个输出中的资金。在真实的比特币系统中,ScriptPubKey可以包含更复杂的脚本,但在这个简化模型中,它被设置为接收方的地址。

解锁脚本(ScriptSig)
// TXOutput 		表示交易输出 type TXOutput struct { 	Value        int    //	输出金额 	ScriptPubKey string //锁定脚本 }

        与此同时,在TXInput结构体中,ScriptSig字段代表解锁脚本。当你尝试花费某个输出时,你需要提供一个ScriptSig,这个脚本能够"解锁"那个输出中的ScriptPubKey所施加的限制。

        在真实的比特币系统中,解锁脚本通常包含与锁定脚本相匹配的公钥和一个签名,这个签名是使用与公钥相对应的私钥生成的。这样,只有拥有正确私钥的人才能生成有效的签名,从而成功解锁输出。

简化说明

        在本文代码示例中,锁定脚本和解锁脚本被简化为直接使用地址字符串。这意味着,如果一个输出的ScriptPubKey与某个地址匹配,那么任何包含相同地址作为ScriptSig的输入都能解锁并花费这个输出。

// CanUnlockOutputWith 将检查输出是否可以使用提供的数据解锁 func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {     return in.ScriptSig == unlockingData }  // CanBeUnlockedWith 将检查输出是否可以使用提供的数据解锁 func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {     return out.ScriptPubKey == unlockingData } 

        这种简化的实现方式有助于理解区块链交易的基本原理,即资产的转移需要通过正确的解锁条件。然而,在实际的区块链应用中,这种机制要复杂得多,涉及到密钥对和加密签名,以确保交易的安全性和用户的隐私。

函数解析

  • IsCoinbase: 判断交易是否为 coinbase 交易。Coinbase 交易是区块中的第一笔交易,用于奖励矿工,其输入为空。
  • SetID: 计算交易的哈希值,并将其设置为交易 ID。
  • CanUnlockOutputWith: 检查交易输入的解锁脚本是否可以解锁指定的锁定脚本。
  • CanBeUnlockedWith: 检查交易输出的锁定脚本是否可以被指定的解锁脚本解锁。
  • NewCoinbaseTX: 创建 coinbase 交易。
    • 输入为空,输出为指定地址和奖励金额。
    • 设置交易 ID。
  • NewUTXOTransaction: 创建普通交易。
    • 根据发送方地址和转账金额,查找满足条件的未花费交易输出 (UTXO)。
    • 如果 UTXO 余额不足,则报错。
    • 否则,构建交易输入和输出列表。
    • 如果 UTXO 余额大于转账金额,则创建找零输出。
    • 设置交易 ID。
  •  IntToHex 将整数转换为字节数组。
// -*- coding: utf-8 -*- // Time    : 2024/4/15 22:16 // Author  : blue // File    : Transaction.go // Software: Goland package main  import ( 	"bytes" 	"crypto/sha256" 	"encoding/gob" 	"encoding/hex" 	"fmt" 	"log" )  // 表示每个区块链的奖励 const subsidy = 10  // Transaction 	表示一笔交易 type Transaction struct { 	ID   []byte     //交易ID,表示交易的哈希 	Vin  []TXInput  //交易输入 	Vout []TXOutput //交易输出 }  // TXInput 		表示交易输入 type TXInput struct { 	Txid      []byte //引用的交易ID,表示交易的哈希 	Vout      int    //引用的输出索引 	ScriptSig string //解锁脚本 }  // TXOutput 		表示交易输出 type TXOutput struct { 	Value        int    //	输出金额 	ScriptPubKey string //锁定脚本 }  // IsCoinbase 将检查交易是否为coinbase交易 func (tx Transaction) IsCoinbase() bool { 	return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 }  // SetID 将设置交易ID,交易ID是交易的哈希 func (tx *Transaction) SetID() { 	//创建一个缓冲区 	var encoded bytes.Buffer 	//创建一个哈希 	var hash [32]byte 	//创建一个编码器 	enc := gob.NewEncoder(&encoded) 	//编码 	err := enc.Encode(tx)  	if err != nil { 		log.Panic(err) 	} 	//计算哈希 	hash = sha256.Sum256(encoded.Bytes()) 	//设置ID 	tx.ID = hash[:] }  // CanUnlockOutputWith 将检查输出是否可以使用提供的数据解锁 func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { 	return in.ScriptSig == unlockingData }  // CanBeUnlockedWith 将检查输出是否可以使用提供的数据解锁 func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { 	return out.ScriptPubKey == unlockingData }  // NewCoinbaseTX 将创建一个新的coinbase交易 func NewCoinbaseTX(to, data string) *Transaction { 	// 如果没有数据,则使用默认数据 	if data == "" { 		data = fmt.Sprintf("Reward to '%s'", to) 	} 	//coinbase交易没有输入,所以Txid为空,Vout为-1 	txin := TXInput{[]byte{}, -1, data} 	//创建一个输出 	txout := TXOutput{subsidy, to} 	//创建一个交易 	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} 	tx.SetID()  	return &tx }  // NewUTXOTransaction 将创建一个新的UTXO交易 func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { 	var inputs []TXInput 	var outputs []TXOutput 	//获取未花费的输出 	acc, validOutputs := bc.FindSpendableOutputs(from, amount) 	//检查余额是否足够 	if acc < amount { 		log.Panic("ERROR: Not enough funds") 	} 	//	构建一个输入列表 	for txid, outs := range validOutputs { 		//将交易ID转换为字节数组 		txID, err := hex.DecodeString(txid) 		if err != nil { 			log.Panic(err) 		} 		//遍历输出 		for _, out := range outs { 			//创建一个输入 			input := TXInput{txID, out, from} 			//添加到输入列表 			inputs = append(inputs, input) 		} 	}  	//创建一个输出 	outputs = append(outputs, TXOutput{amount, to}) 	//如果余额大于转账金额,则创建一个找零 	if acc > amount { 		outputs = append(outputs, TXOutput{acc - amount, from}) // a change 	} 	//创建一个交易 	tx := Transaction{nil, inputs, outputs} 	tx.SetID()  	return &tx }  // 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() } 

5.cli.go

这段代码实现了一个简单的命令行界面 (CLI),用于与区块链进行交互,包括创建区块链、查询余额、打印区块链和发送交易等功能。让我们逐一解析:

CLI 结构体

  • CLI 结构体用于处理命令行参数和执行相应操作。

函数解析

  • createBlockchain: 创建新的区块链,并将创世区块奖励发送到指定地址。
  • getBalance: 查询指定地址的余额。
  • printUsage: 打印命令行用法帮助信息。
  • validateArgs: 验证命令行参数是否合法。
  • printChain: 打印整个区块链的信息,包括每个区块的哈希、时间戳、交易等。
  • send: 从一个地址向另一个地址发送指定数量的币。
  • run: 运行命令行界面,循环提示用户输入命令,并根据命令执行相应操作。
  • addBlock: 添加新区块到区块链。
// -*- coding: utf-8 -*- // Time    : 2024/4/15 22:18 // Author  : blue // File    : cli.go // Software: Goland package main  import ( 	"fmt" 	"os" 	"strconv" 	"time" )  // CLI 处理命令行参数 type CLI struct { }  // createBlockchain 创建区块链 func (cli *CLI) createBlockchain(address string) { 	// 创建区块链 	bc := CreateBlockchain(address) 	bc.db.Close() 	fmt.Println("Done!") }  // getBalance 获取地址的余额 func (cli *CLI) getBalance(address string) { 	// 创建区块链 	bc := NewBlockchain(address) 	defer bc.db.Close() 	balance := 0 	// 获取地址的UTXO 	UTXOs := bc.FindUTXO(address) 	// 计算余额 	for _, out := range UTXOs { 		balance += out.Value 	}  	fmt.Printf("Balance of '%s': %d\n", address, balance) }  // printUsage 打印用法 func (cli *CLI) printUsage() { 	fmt.Println("Usage:") 	fmt.Println(" English: getbalance -address ADDRESS - Get balance of ADDRESS、、、中文:getbalance - address ADDRESS -获取地址的余额") 	fmt.Println("  createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") 	fmt.Println("  printchain - Print all the blocks of the blockchain") 	fmt.Println("  send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") }  // validateArgs 验证参数 func (cli *CLI) validateArgs() { 	// 验证参数 	if len(os.Args) < 2 { //什么意思:如果命令行参数少于2个 		// 打印用法 		cli.printUsage() 		os.Exit(1) //什么意思:退出 	} }  // printChain 打印区块链 func (cli *CLI) printChain() { 	// 创建区块链 	bc := NewBlockchain("") 	defer bc.db.Close() 	// 创建迭代器 	bci := bc.Iterator() 	// 循环打印区块链中的区块 	for { 		block := bci.Next() 		fmt.Println("=========================================") 		//把时间戳转换为时间 		timeFormat := time.Unix(block.Timestamp, 0) 		fmt.Println("timestamp:", timeFormat) 		fmt.Printf("PrevBlockHash: %x\n", block.PrevBlockHash) 		fmt.Printf("BlockHash: %x\n", block.Hash) 		//显示完整的交易信息 		fmt.Println("Transactions:") 		for _, tx := range block.Transactions { 			//显示交易ID 			//fmt.Printf("Transaction ID: %x\n", tx.ID) 			var str string 			for _, value := range tx.ID { 				str += strconv.Itoa(int(value)) 			} 			fmt.Println("Transaction ID: " + str) 			fmt.Println("TXInput:") 			for _, tx := range tx.Vin { 				fmt.Println(tx.Txid) 				fmt.Println(tx.Vout) 				fmt.Println(tx.ScriptSig)  			} 			fmt.Println("TXOutputs:") 			for _, tx := range tx.Vout { 				fmt.Println(tx.Value) 				fmt.Println(tx.ScriptPubKey) 			} 		} 		//fmt.Println("Transactions:", block.Transactions) 		fmt.Printf("Nonce: %d\n", block.Nonce) 		pow := NewProofOfWork(block) 		fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 		fmt.Println()  		if len(block.PrevBlockHash) == 0 { 			break 		} 	} }  // send 发送 func (cli *CLI) send(from, to string, amount int) { 	bc := NewBlockchain(from) 	defer bc.db.Close() 	// 创建交易 	tx := NewUTXOTransaction(from, to, amount, bc) 	// 挖矿 	bc.MineBlock([]*Transaction{tx}) 	fmt.Println("Success!") }  // 新建一个run函数,实现在程序框显示功能,并给出提示,然后根据相应的命令执行相应的操作 func (cli *CLI) run() { 	for { 		fmt.Println("1. getbalance -address ADDRESS - 获取地址的余额 ") 		fmt.Println("2. createblockchain -address ADDRESS - 创建区块链并将创世区块奖励发送到ADDRESS") 		fmt.Println("3. printchain - 打印区块链") 		fmt.Println("4. send -from FROM -to TO -amount AMOUNT - 从FROM地址向TO发送AMOUNT硬币") 		fmt.Println("5. mine ADDRESS挖矿创建区块链并将创世区块奖励发送到ADDRESS") 		fmt.Println("6. exit - Exit") 		fmt.Println("Please enter the command:") 		var cmd string 		fmt.Scanln(&cmd) 		switch cmd { 		case "1": 			fmt.Println("Please enter the address:") 			var address string 			fmt.Scanln(&address) 			cli.getBalance(address) 			fmt.Println() 		case "2": 			fmt.Println("Please enter the address:") 			var address string 			fmt.Scanln(&address) 			cli.createBlockchain(address) 			fmt.Println() 		case "3": 			cli.printChain() 		case "4": 			fmt.Println("Please enter the from address:") 			var from string 			fmt.Scanln(&from) 			fmt.Println("Please enter the to address:") 			var to string 			fmt.Scanln(&to) 			fmt.Println("Please enter the amount:") 			var amount int 			fmt.Scanln(&amount) 			cli.send(from, to, amount) 			fmt.Println() 		case "5": 			fmt.Println("Please enter the address:") 			var address string 			fmt.Scanln(&address) 			//我们要创建新区块 			//挖掘新区块 			//AddBlock(address) 			cli.addBlock(address) 			fmt.Println() 		case "6": 			os.Exit(0) 		default: 			fmt.Println("Invalid command") 		} 	} }  // addBlock 添加区块 func (cli *CLI) addBlock(address string) { 	// 创建区块链 	AddBlock(address) } 

6.main.go

这段代码是整个区块链项目的入口,它创建了一个 CLI 对象,并调用其 run 方法启动命令行界面。

  • main 函数: 程序的入口函数。
  • cli := CLI{}: 创建一个 CLI 对象,用于处理命令行参数和执行相应操作。
  • cli.run(): 调用 CLI 对象的 run 方法,启动命令行界面,并开始与用户交互。
// -*- coding: utf-8 -*- // Time    : 2024/4/15 22:15 // Author  : blue // File    : main.go // Software: Goland package main  func main() { 	cli := CLI{} 	//cli.Run() 	cli.run() } 

总结

      通过本篇博客,我们详细探讨了如何使用Go语言实现一个简易的区块链系统。我们从区块的数据结构出发,探讨了区块链的创建、交易的处理、工作量证明算法的实现,以及通过CLI与区块链进行交互的方法。此外,我们还了解了如何使用BoltDB对区块链数据进行持久化存储,这对于保证区块链数据的持久性和可靠性至关重要。

        实现一个区块链系统虽然复杂,但通过逐步分析和实现,我们可以更好地理解其背后的原理和技术。希望这篇博客能为那些对区块链技术感兴趣的读者提供一定的帮助,并激发大家进一步探索和实践区块链技术的热情。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!