当前位置:   article > 正文

Go语言实现区块链——添加coinbase交易及UTXO_go语言区块链框架

go语言区块链框架

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

建议观看顺序:

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

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

1.block.go

区块的序列化与反序列化

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

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

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

区块的创建

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

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

  1. // -*- coding: utf-8 -*-
  2. // Time : 2024/4/15 22:15
  3. // Author : blue
  4. // File : block.go
  5. // Software: Goland
  6. package main
  7. import (
  8. "bytes"
  9. "crypto/sha256"
  10. "encoding/gob"
  11. "log"
  12. "time"
  13. )
  14. // Block keeps block headers
  15. type Block struct {
  16. Timestamp int64 // 时间戳
  17. Transactions []*Transaction // 交易
  18. PrevBlockHash []byte // 前一个区块的哈希
  19. Hash []byte // 当前区块的哈希
  20. Nonce int // 随机数
  21. }
  22. // Serializes 是将区块序列化为字节数组
  23. func (b *Block) Serialize() []byte {
  24. var result bytes.Buffer
  25. encoder := gob.NewEncoder(&result)
  26. err := encoder.Encode(b)
  27. if err != nil {
  28. log.Panic(err)
  29. }
  30. return result.Bytes()
  31. }
  32. // HashTransactions将区块中的所有交易序列化并返回一个哈希值
  33. func (b *Block) HashTransactions() []byte {
  34. var txHashes [][]byte
  35. var txHash [32]byte
  36. // 遍历区块中的所有交易
  37. for _, tx := range b.Transactions {
  38. txHashes = append(txHashes, tx.ID)
  39. }
  40. txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))
  41. return txHash[:]
  42. }
  43. // NewBlock 将创建一个新的区块
  44. func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
  45. // 创建区块
  46. block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
  47. // 创建工作量证明
  48. pow := NewProofOfWork(block)
  49. // 运行工作量证明
  50. nonce, hash := pow.Run()
  51. block.Hash = hash[:]
  52. block.Nonce = nonce
  53. return block
  54. }
  55. // NewGenesisBlock 将创建并返回创世区块
  56. func NewGenesisBlock(coinbase *Transaction) *Block {
  57. return NewBlock([]*Transaction{coinbase}, []byte{})
  58. }
  59. // DeserializeBlock 将区块的字节数组反序列化为区块
  60. func DeserializeBlock(d []byte) *Block {
  61. var block Block
  62. decoder := gob.NewDecoder(bytes.NewReader(d))
  63. err := decoder.Decode(&block)
  64. if err != nil {
  65. log.Panic(err)
  66. }
  67. return &block
  68. }

 2.blockchains.go

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

数据结构

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

函数解析

  • MineBlock: 挖矿函数,根据交易列表创建新区块,并将其添加到区块链中。
    • 首先获取最新区块的哈希值。
    • 然后创建新的区块,并进行工作量证明计算。
    • 最后将新区块序列化并存储到数据库中,更新区块链的最新区块哈希值。
  • FindUnspentTransactions: 查找指定地址的未花费交易。
    • 遍历区块链,检查每个交易的输出是否属于指定地址,以及是否已被花费。
    • 将未花费的交易添加到结果列表中。
  • FindUTXO: 查找指定地址的未花费交易输出 (UTXO)。
    • 调用 FindUnspentTransactions 获取未花费交易。
    • 遍历未花费交易的输出,将属于指定地址的输出添加到结果列表中。
  • FindSpendableOutputs: 查找满足指定金额的未花费交易输出。
    • 遍历未花费交易的输出,累加金额,直到满足指定金额。
    • 返回累加金额和满足条件的未花费交易输出。
  • Iterator: 创建区块链迭代器,从最新区块开始遍历区块链。
  • Next: 获取区块链迭代器指向的当前区块,并将迭代器指向下一个区块。
  • dbExists: 检查区块链数据库文件是否存在。
  • NewBlockchain: 打开区块链数据库,并创建区块链对象。
  • CreateBlockchain: 创建新的区块链,包含创世区块。
    • 首先创建 coinbase 交易,奖励给指定地址。
    • 然后创建创世区块,包含 coinbase 交易。
    • 最后将创世区块存储到数据库中,并创建区块链对象。
  • AddBlock: 添加新区块到区块链。
    • 首先创建 coinbase 交易,奖励给指定地址。
    • 然后调用MineBlock挖矿并添加新区块到区块链中。
  1. // -*- coding: utf-8 -*-
  2. // Time : 2024/4/15 22:15
  3. // Author : blue
  4. // File : blockchains.go
  5. // Software: Goland
  6. package main
  7. import (
  8. "encoding/hex"
  9. "fmt"
  10. "github.com/boltdb/bolt"
  11. "log"
  12. "os"
  13. )
  14. const dbFile = "blockchain.db" //数据库文件
  15. const blocksBucket = "blocks" //存储区块的桶
  16. const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" //创世块的交易数据
  17. // Blockchain 包含一个区块链
  18. type Blockchain struct {
  19. tip []byte //最新区块的哈希
  20. db *bolt.DB //数据库指针
  21. }
  22. // BlockchainIterator 将用于迭代区块链
  23. type BlockchainIterator struct {
  24. currentHash []byte //当前区块的哈希
  25. db *bolt.DB //数据库指针
  26. }
  27. // MineBlock 将用于挖掘新块
  28. func (bc *Blockchain) MineBlock(transactions []*Transaction) {
  29. var lastHash []byte //记录最新区块的哈希
  30. //获取最新区块的哈希
  31. err := bc.db.View(func(tx *bolt.Tx) error {
  32. //获取区块桶
  33. b := tx.Bucket([]byte(blocksBucket))
  34. //获取最新区块的哈希
  35. lastHash = b.Get([]byte("l"))
  36. return nil
  37. })
  38. if err != nil {
  39. log.Panic(err)
  40. }
  41. //创建新区块(包含验证)
  42. newBlock := NewBlock(transactions, lastHash)
  43. //将新区块存储到数据库中
  44. err = bc.db.Update(func(tx *bolt.Tx) error {
  45. //获取区块桶
  46. b := tx.Bucket([]byte(blocksBucket))
  47. //将新区块存储到数据库中
  48. err := b.Put(newBlock.Hash, newBlock.Serialize())
  49. if err != nil {
  50. log.Panic(err)
  51. }
  52. //将最新区块的哈希存储到数据库中
  53. err = b.Put([]byte("l"), newBlock.Hash)
  54. if err != nil {
  55. log.Panic(err)
  56. }
  57. //更新区块链的tip
  58. bc.tip = newBlock.Hash
  59. return nil
  60. })
  61. }
  62. // FindUnspentTransactions 返回未花费的交易
  63. func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
  64. //未花费的交易
  65. var unspentTXs []Transaction
  66. //已花费的输出
  67. spentTXOs := make(map[string][]int)
  68. //迭代区块链
  69. bci := bc.Iterator()
  70. for {
  71. //获取下一个区块
  72. block := bci.Next()
  73. //遍历区块中的交易
  74. for _, tx := range block.Transactions {
  75. //将交易ID转换为字符串
  76. txID := hex.EncodeToString(tx.ID)
  77. Outputs:
  78. //遍历交易中的输出
  79. for outIdx, out := range tx.Vout {
  80. //检查输出是否已经被花费
  81. if spentTXOs[txID] != nil {
  82. //遍历已花费的输出
  83. for _, spentOut := range spentTXOs[txID] {
  84. //如果输出已经被花费,则跳过
  85. if spentOut == outIdx {
  86. continue Outputs
  87. }
  88. }
  89. }
  90. //如果输出可以被解锁,则将交易添加到未花费的交易中
  91. if out.CanBeUnlockedWith(address) {
  92. unspentTXs = append(unspentTXs, *tx)
  93. }
  94. }
  95. //如果交易不是coinbase交易,则遍历交易的输入
  96. if tx.IsCoinbase() == false {
  97. //遍历交易的输入
  98. for _, in := range tx.Vin {
  99. //如果输入可以解锁,则将输出添加到已花费的输出中
  100. if in.CanUnlockOutputWith(address) {
  101. //将交易ID转换为字符串
  102. inTxID := hex.EncodeToString(in.Txid)
  103. //将输出添加到已花费的输出中
  104. spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
  105. }
  106. }
  107. }
  108. }
  109. //如果区块的前一个区块哈希为空,则退出循环
  110. if len(block.PrevBlockHash) == 0 {
  111. break
  112. }
  113. }
  114. //返回未花费的交易
  115. return unspentTXs
  116. }
  117. // FindUTXO 返回未花费的输出
  118. func (bc *Blockchain) FindUTXO(address string) []TXOutput {
  119. var UTXOs []TXOutput
  120. //未花费的交易
  121. unspentTransactions := bc.FindUnspentTransactions(address)
  122. //遍历未花费的交易
  123. for _, tx := range unspentTransactions {
  124. //遍历交易的输出
  125. for _, out := range tx.Vout {
  126. //如果输出可以被解锁,则将输出添加到未花费的输出中
  127. if out.CanBeUnlockedWith(address) {
  128. UTXOs = append(UTXOs, out)
  129. }
  130. }
  131. }
  132. return UTXOs
  133. }
  134. // FindSpendableOutputs 返回足够的未花费输出以满足要求的金额
  135. func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
  136. //未花费的输出
  137. unspentOutputs := make(map[string][]int)
  138. //未花费的交易
  139. unspentTXs := bc.FindUnspentTransactions(address)
  140. //累计金额
  141. accumulated := 0
  142. Work:
  143. //遍历未花费的交易
  144. for _, tx := range unspentTXs {
  145. //将交易ID转换为字符串
  146. txID := hex.EncodeToString(tx.ID)
  147. //遍历交易的输出
  148. for outIdx, out := range tx.Vout {
  149. //如果输出可以被解锁且累计金额小于要求的金额,则将输出添加到未花费的输出中
  150. if out.CanBeUnlockedWith(address) && accumulated < amount {
  151. //将输出添加到未花费的输出中
  152. accumulated += out.Value
  153. //将输出的索引添加到未花费的输出中
  154. unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
  155. //如果累计金额大于等于要求的金额,则退出循环
  156. if accumulated >= amount {
  157. break Work
  158. }
  159. }
  160. }
  161. }
  162. //返回累计金额和未花费的输出
  163. return accumulated, unspentOutputs
  164. }
  165. // Iterator 返回一个迭代器
  166. func (bc *Blockchain) Iterator() *BlockchainIterator {
  167. //迭代器对象是一个指向区块链的指针和一个指向数据库的指针
  168. bci := &BlockchainIterator{bc.tip, bc.db}
  169. return bci
  170. }
  171. // Next 返回区块链中的下一个区块
  172. func (i *BlockchainIterator) Next() *Block {
  173. var block *Block
  174. //获取当前区块
  175. err := i.db.View(func(tx *bolt.Tx) error {
  176. //获取区块桶
  177. b := tx.Bucket([]byte(blocksBucket))
  178. //获取当前区块
  179. encodedBlock := b.Get(i.currentHash)
  180. //反序列化区块
  181. block = DeserializeBlock(encodedBlock)
  182. return nil
  183. })
  184. if err != nil {
  185. log.Panic(err)
  186. }
  187. //更新当前区块的哈希
  188. i.currentHash = block.PrevBlockHash
  189. return block
  190. }
  191. // dbExists 检查数据库是否存在
  192. func dbExists() bool {
  193. if _, err := os.Stat(dbFile); os.IsNotExist(err) {
  194. return false
  195. }
  196. return true
  197. }
  198. // NewBlockchain 创建一个新的区块
  199. func NewBlockchain(address string) *Blockchain {
  200. //检查数据库是否存在
  201. if dbExists() == false {
  202. fmt.Println("No existing blockchain found. Create one first.")
  203. os.Exit(1)
  204. }
  205. //存储最新区块的哈希
  206. var tip []byte
  207. db, err := bolt.Open(dbFile, 0600, nil)
  208. if err != nil {
  209. log.Panic(err)
  210. }
  211. //更新数据库
  212. err = db.Update(func(tx *bolt.Tx) error {
  213. b := tx.Bucket([]byte(blocksBucket))
  214. tip = b.Get([]byte("l"))
  215. return nil
  216. })
  217. if err != nil {
  218. log.Panic(err)
  219. }
  220. //创建一个区块链对象
  221. bc := Blockchain{tip, db}
  222. return &bc
  223. }
  224. // CreateBlockchain 创建一个新的区块链
  225. func CreateBlockchain(address string) *Blockchain {
  226. //检查数据库是否存在
  227. if dbExists() {
  228. fmt.Println("Blockchain already exists.")
  229. os.Exit(1)
  230. }
  231. var tip []byte
  232. //打开数据库
  233. db, err := bolt.Open(dbFile, 0600, nil)
  234. if err != nil {
  235. log.Panic(err)
  236. }
  237. //更新数据库
  238. err = db.Update(func(tx *bolt.Tx) error {
  239. //创建一个新的coinbase交易
  240. cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
  241. //创建一个新的区块
  242. genesis := NewGenesisBlock(cbtx)
  243. //创建一个新的桶
  244. b, err := tx.CreateBucket([]byte(blocksBucket))
  245. if err != nil {
  246. log.Panic(err)
  247. }
  248. //将区块存储到数据库中
  249. err = b.Put(genesis.Hash, genesis.Serialize())
  250. if err != nil {
  251. log.Panic(err)
  252. }
  253. err = b.Put([]byte("l"), genesis.Hash)
  254. if err != nil {
  255. log.Panic(err)
  256. }
  257. tip = genesis.Hash
  258. return nil
  259. })
  260. if err != nil {
  261. log.Panic(err)
  262. }
  263. bc := Blockchain{tip, db}
  264. return &bc
  265. }
  266. // AddBlock 将用于添加区块到区块链
  267. func AddBlock(address string) {
  268. //创建一个新的区块
  269. bc := NewBlockchain(address)
  270. defer bc.db.Close()
  271. //创建一个新的coinbase交易
  272. //cbtx := NewCoinbaseTX(address, "")
  273. //挖掘新块并存放到数据库中
  274. bc.MineBlock([]*Transaction{NewCoinbaseTX(address, "")})
  275. //往address地址发送奖励
  276. fmt.Println("Coinbase交易完成,以奖励的方式将硬币发送到地址:", address)
  277. fmt.Println("Success!")
  278. }

 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
  1. // -*- coding: utf-8 -*-
  2. // Time : 2024/4/14 23:48
  3. // Author : blue
  4. // File : proofwork.go
  5. // Software: Goland
  6. package main
  7. import (
  8. "bytes"
  9. "crypto/sha256"
  10. "fmt"
  11. "math"
  12. "math/big"
  13. )
  14. var (
  15. maxNonce = math.MaxInt64
  16. )
  17. const targetBits = 16
  18. // ProofOfWork 包含一个指向区块的指针和一个指向big.Int的指针
  19. type ProofOfWork struct {
  20. block *Block
  21. target *big.Int
  22. }
  23. // NewProofOfWork 创建并返回一个ProofOfWork结构
  24. func NewProofOfWork(b *Block) *ProofOfWork {
  25. target := big.NewInt(1)
  26. target.Lsh(target, uint(256-targetBits))
  27. pow := &ProofOfWork{b, target}
  28. return pow
  29. }
  30. // prepareData 准备数据
  31. func (pow *ProofOfWork) prepareData(nonce int) []byte {
  32. data := bytes.Join(
  33. [][]byte{
  34. pow.block.PrevBlockHash,
  35. pow.block.HashTransactions(),
  36. IntToHex(pow.block.Timestamp),
  37. IntToHex(int64(targetBits)),
  38. IntToHex(int64(nonce)),
  39. },
  40. []byte{},
  41. )
  42. return data
  43. }
  44. // Run 查找有效的哈希
  45. func (pow *ProofOfWork) Run() (int, []byte) {
  46. var hashInt big.Int
  47. var hash [32]byte
  48. nonce := 0
  49. fmt.Printf("Mining a new block")
  50. for nonce < maxNonce {
  51. data := pow.prepareData(nonce)
  52. hash = sha256.Sum256(data)
  53. fmt.Printf("\r%x", hash)
  54. hashInt.SetBytes(hash[:])
  55. if hashInt.Cmp(pow.target) == -1 {
  56. break
  57. } else {
  58. nonce++
  59. }
  60. }
  61. fmt.Print("\n\n")
  62. return nonce, hash[:]
  63. }
  64. // Validate 验证工作量证明
  65. func (pow *ProofOfWork) Validate() bool {
  66. var hashInt big.Int
  67. data := pow.prepareData(pow.block.Nonce)
  68. hash := sha256.Sum256(data)
  69. hashInt.SetBytes(hash[:])
  70. isValid := hashInt.Cmp(pow.target) == -1
  71. return isValid
  72. }

4.Transaction.go

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

数据结构

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

拓展:

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

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

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

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

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

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

简化说明

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

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

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

函数解析

  • IsCoinbase: 判断交易是否为 coinbase 交易。Coinbase 交易是区块中的第一笔交易,用于奖励矿工,其输入为空。
  • SetID: 计算交易的哈希值,并将其设置为交易 ID。
  • CanUnlockOutputWith: 检查交易输入的解锁脚本是否可以解锁指定的锁定脚本。
  • CanBeUnlockedWith: 检查交易输出的锁定脚本是否可以被指定的解锁脚本解锁。
  • NewCoinbaseTX: 创建 coinbase 交易。
    • 输入为空,输出为指定地址和奖励金额。
    • 设置交易 ID。
  • NewUTXOTransaction: 创建普通交易。
    • 根据发送方地址和转账金额,查找满足条件的未花费交易输出 (UTXO)。
    • 如果 UTXO 余额不足,则报错。
    • 否则,构建交易输入和输出列表。
    • 如果 UTXO 余额大于转账金额,则创建找零输出。
    • 设置交易 ID。
  •  IntToHex 将整数转换为字节数组。
  1. // -*- coding: utf-8 -*-
  2. // Time : 2024/4/15 22:16
  3. // Author : blue
  4. // File : Transaction.go
  5. // Software: Goland
  6. package main
  7. import (
  8. "bytes"
  9. "crypto/sha256"
  10. "encoding/gob"
  11. "encoding/hex"
  12. "fmt"
  13. "log"
  14. )
  15. // 表示每个区块链的奖励
  16. const subsidy = 10
  17. // Transaction 表示一笔交易
  18. type Transaction struct {
  19. ID []byte //交易ID,表示交易的哈希
  20. Vin []TXInput //交易输入
  21. Vout []TXOutput //交易输出
  22. }
  23. // TXInput 表示交易输入
  24. type TXInput struct {
  25. Txid []byte //引用的交易ID,表示交易的哈希
  26. Vout int //引用的输出索引
  27. ScriptSig string //解锁脚本
  28. }
  29. // TXOutput 表示交易输出
  30. type TXOutput struct {
  31. Value int // 输出金额
  32. ScriptPubKey string //锁定脚本
  33. }
  34. // IsCoinbase 将检查交易是否为coinbase交易
  35. func (tx Transaction) IsCoinbase() bool {
  36. return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
  37. }
  38. // SetID 将设置交易ID,交易ID是交易的哈希
  39. func (tx *Transaction) SetID() {
  40. //创建一个缓冲区
  41. var encoded bytes.Buffer
  42. //创建一个哈希
  43. var hash [32]byte
  44. //创建一个编码器
  45. enc := gob.NewEncoder(&encoded)
  46. //编码
  47. err := enc.Encode(tx)
  48. if err != nil {
  49. log.Panic(err)
  50. }
  51. //计算哈希
  52. hash = sha256.Sum256(encoded.Bytes())
  53. //设置ID
  54. tx.ID = hash[:]
  55. }
  56. // CanUnlockOutputWith 将检查输出是否可以使用提供的数据解锁
  57. func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
  58. return in.ScriptSig == unlockingData
  59. }
  60. // CanBeUnlockedWith 将检查输出是否可以使用提供的数据解锁
  61. func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
  62. return out.ScriptPubKey == unlockingData
  63. }
  64. // NewCoinbaseTX 将创建一个新的coinbase交易
  65. func NewCoinbaseTX(to, data string) *Transaction {
  66. // 如果没有数据,则使用默认数据
  67. if data == "" {
  68. data = fmt.Sprintf("Reward to '%s'", to)
  69. }
  70. //coinbase交易没有输入,所以Txid为空,Vout为-1
  71. txin := TXInput{[]byte{}, -1, data}
  72. //创建一个输出
  73. txout := TXOutput{subsidy, to}
  74. //创建一个交易
  75. tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
  76. tx.SetID()
  77. return &tx
  78. }
  79. // NewUTXOTransaction 将创建一个新的UTXO交易
  80. func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
  81. var inputs []TXInput
  82. var outputs []TXOutput
  83. //获取未花费的输出
  84. acc, validOutputs := bc.FindSpendableOutputs(from, amount)
  85. //检查余额是否足够
  86. if acc < amount {
  87. log.Panic("ERROR: Not enough funds")
  88. }
  89. // 构建一个输入列表
  90. for txid, outs := range validOutputs {
  91. //将交易ID转换为字节数组
  92. txID, err := hex.DecodeString(txid)
  93. if err != nil {
  94. log.Panic(err)
  95. }
  96. //遍历输出
  97. for _, out := range outs {
  98. //创建一个输入
  99. input := TXInput{txID, out, from}
  100. //添加到输入列表
  101. inputs = append(inputs, input)
  102. }
  103. }
  104. //创建一个输出
  105. outputs = append(outputs, TXOutput{amount, to})
  106. //如果余额大于转账金额,则创建一个找零
  107. if acc > amount {
  108. outputs = append(outputs, TXOutput{acc - amount, from}) // a change
  109. }
  110. //创建一个交易
  111. tx := Transaction{nil, inputs, outputs}
  112. tx.SetID()
  113. return &tx
  114. }
  115. // IntToHex 将整数转换为字节数组
  116. func IntToHex(num int64) []byte {
  117. buff := new(bytes.Buffer)
  118. err := binary.Write(buff, binary.BigEndian, num)
  119. if err != nil {
  120. log.Panic(err)
  121. }
  122. return buff.Bytes()
  123. }

5.cli.go

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

CLI 结构体

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

函数解析

  • createBlockchain: 创建新的区块链,并将创世区块奖励发送到指定地址。
  • getBalance: 查询指定地址的余额。
  • printUsage: 打印命令行用法帮助信息。
  • validateArgs: 验证命令行参数是否合法。
  • printChain: 打印整个区块链的信息,包括每个区块的哈希、时间戳、交易等。
  • send: 从一个地址向另一个地址发送指定数量的币。
  • run: 运行命令行界面,循环提示用户输入命令,并根据命令执行相应操作。
  • addBlock: 添加新区块到区块链。
  1. // -*- coding: utf-8 -*-
  2. // Time : 2024/4/15 22:18
  3. // Author : blue
  4. // File : cli.go
  5. // Software: Goland
  6. package main
  7. import (
  8. "fmt"
  9. "os"
  10. "strconv"
  11. "time"
  12. )
  13. // CLI 处理命令行参数
  14. type CLI struct {
  15. }
  16. // createBlockchain 创建区块链
  17. func (cli *CLI) createBlockchain(address string) {
  18. // 创建区块链
  19. bc := CreateBlockchain(address)
  20. bc.db.Close()
  21. fmt.Println("Done!")
  22. }
  23. // getBalance 获取地址的余额
  24. func (cli *CLI) getBalance(address string) {
  25. // 创建区块链
  26. bc := NewBlockchain(address)
  27. defer bc.db.Close()
  28. balance := 0
  29. // 获取地址的UTXO
  30. UTXOs := bc.FindUTXO(address)
  31. // 计算余额
  32. for _, out := range UTXOs {
  33. balance += out.Value
  34. }
  35. fmt.Printf("Balance of '%s': %d\n", address, balance)
  36. }
  37. // printUsage 打印用法
  38. func (cli *CLI) printUsage() {
  39. fmt.Println("Usage:")
  40. fmt.Println(" English: getbalance -address ADDRESS - Get balance of ADDRESS、、、中文:getbalance - address ADDRESS -获取地址的余额")
  41. fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS")
  42. fmt.Println(" printchain - Print all the blocks of the blockchain")
  43. fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO")
  44. }
  45. // validateArgs 验证参数
  46. func (cli *CLI) validateArgs() {
  47. // 验证参数
  48. if len(os.Args) < 2 { //什么意思:如果命令行参数少于2个
  49. // 打印用法
  50. cli.printUsage()
  51. os.Exit(1) //什么意思:退出
  52. }
  53. }
  54. // printChain 打印区块链
  55. func (cli *CLI) printChain() {
  56. // 创建区块链
  57. bc := NewBlockchain("")
  58. defer bc.db.Close()
  59. // 创建迭代器
  60. bci := bc.Iterator()
  61. // 循环打印区块链中的区块
  62. for {
  63. block := bci.Next()
  64. fmt.Println("=========================================")
  65. //把时间戳转换为时间
  66. timeFormat := time.Unix(block.Timestamp, 0)
  67. fmt.Println("timestamp:", timeFormat)
  68. fmt.Printf("PrevBlockHash: %x\n", block.PrevBlockHash)
  69. fmt.Printf("BlockHash: %x\n", block.Hash)
  70. //显示完整的交易信息
  71. fmt.Println("Transactions:")
  72. for _, tx := range block.Transactions {
  73. //显示交易ID
  74. //fmt.Printf("Transaction ID: %x\n", tx.ID)
  75. var str string
  76. for _, value := range tx.ID {
  77. str += strconv.Itoa(int(value))
  78. }
  79. fmt.Println("Transaction ID: " + str)
  80. fmt.Println("TXInput:")
  81. for _, tx := range tx.Vin {
  82. fmt.Println(tx.Txid)
  83. fmt.Println(tx.Vout)
  84. fmt.Println(tx.ScriptSig)
  85. }
  86. fmt.Println("TXOutputs:")
  87. for _, tx := range tx.Vout {
  88. fmt.Println(tx.Value)
  89. fmt.Println(tx.ScriptPubKey)
  90. }
  91. }
  92. //fmt.Println("Transactions:", block.Transactions)
  93. fmt.Printf("Nonce: %d\n", block.Nonce)
  94. pow := NewProofOfWork(block)
  95. fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
  96. fmt.Println()
  97. if len(block.PrevBlockHash) == 0 {
  98. break
  99. }
  100. }
  101. }
  102. // send 发送
  103. func (cli *CLI) send(from, to string, amount int) {
  104. bc := NewBlockchain(from)
  105. defer bc.db.Close()
  106. // 创建交易
  107. tx := NewUTXOTransaction(from, to, amount, bc)
  108. // 挖矿
  109. bc.MineBlock([]*Transaction{tx})
  110. fmt.Println("Success!")
  111. }
  112. // 新建一个run函数,实现在程序框显示功能,并给出提示,然后根据相应的命令执行相应的操作
  113. func (cli *CLI) run() {
  114. for {
  115. fmt.Println("1. getbalance -address ADDRESS - 获取地址的余额 ")
  116. fmt.Println("2. createblockchain -address ADDRESS - 创建区块链并将创世区块奖励发送到ADDRESS")
  117. fmt.Println("3. printchain - 打印区块链")
  118. fmt.Println("4. send -from FROM -to TO -amount AMOUNT - 从FROM地址向TO发送AMOUNT硬币")
  119. fmt.Println("5. mine ADDRESS挖矿创建区块链并将创世区块奖励发送到ADDRESS")
  120. fmt.Println("6. exit - Exit")
  121. fmt.Println("Please enter the command:")
  122. var cmd string
  123. fmt.Scanln(&cmd)
  124. switch cmd {
  125. case "1":
  126. fmt.Println("Please enter the address:")
  127. var address string
  128. fmt.Scanln(&address)
  129. cli.getBalance(address)
  130. fmt.Println()
  131. case "2":
  132. fmt.Println("Please enter the address:")
  133. var address string
  134. fmt.Scanln(&address)
  135. cli.createBlockchain(address)
  136. fmt.Println()
  137. case "3":
  138. cli.printChain()
  139. case "4":
  140. fmt.Println("Please enter the from address:")
  141. var from string
  142. fmt.Scanln(&from)
  143. fmt.Println("Please enter the to address:")
  144. var to string
  145. fmt.Scanln(&to)
  146. fmt.Println("Please enter the amount:")
  147. var amount int
  148. fmt.Scanln(&amount)
  149. cli.send(from, to, amount)
  150. fmt.Println()
  151. case "5":
  152. fmt.Println("Please enter the address:")
  153. var address string
  154. fmt.Scanln(&address)
  155. //我们要创建新区块
  156. //挖掘新区块
  157. //AddBlock(address)
  158. cli.addBlock(address)
  159. fmt.Println()
  160. case "6":
  161. os.Exit(0)
  162. default:
  163. fmt.Println("Invalid command")
  164. }
  165. }
  166. }
  167. // addBlock 添加区块
  168. func (cli *CLI) addBlock(address string) {
  169. // 创建区块链
  170. AddBlock(address)
  171. }

6.main.go

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

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

总结

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/931488
推荐阅读
相关标签
  

闽ICP备14008679号