赞
踩
在本篇博客中,我们将深入探讨一个用Go语言编写的简易区块链实现。通过这个示例,我们可以理解区块链背后的核心概念,包括如何创建和管理区块、如何处理交易以及如何通过工作量证明算法保证区块链的安全性。我们还会探讨如何使用BoltDB这个轻量级的键值数据库来持久化存储区块链数据。在这个过程中,我们将一步一步构建起区块链的基本结构,并演示如何通过命令行界面(CLI)与区块链进行交互。
建议观看顺序:
为了能够在网络中传输或在磁盘上存储,我们需要将区块序列化和反序列化。序列化是将区块转换为字节序列的过程,而反序列化是将字节序列还原为原始区块的过程。
序列化 (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
- }
这段代码实现了区块链的核心数据结构和功能,包括区块链、区块迭代器以及创建、查询和添加区块等操作。让我们深入分析:
FindUnspentTransactions
获取未花费交易。- // -*- 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!")
- }
这段代码实现了区块链中的工作量证明 (Proof of Work, PoW) 机制,用于确保区块链的安全性,防止恶意攻击。让我们深入理解其原理和实现:
PoW 是一种共识机制,要求节点进行一定的计算工作才能将新区块添加到区块链中。这种机制通过消耗计算资源来增加攻击成本,从而保证区块链的安全性。
ProofOfWork 结构体:
block
: 指向区块的指针,包含区块头信息。target
: 指向一个大整数的指针,代表挖矿难度目标。哈希值必须小于该目标才算有效。NewProofOfWork:
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
- }
这段代码定义了区块链中的交易数据结构,包括交易输入、交易输出和交易本身,以及创建 coinbase 交易和普通交易的函数。让我们深入分析:
在区块链技术,特别是比特币及其衍生加密货币中,交易的验证过程涉及到两个重要的概念:锁定脚本(ScriptPubKey)和解锁脚本(ScriptSig)。这两个脚本共同工作,确保只有资产的合法拥有者才能花费这些资产。在本文Transaction.go
代码示例中,为了便于理解和实现,锁定脚本和解锁脚本简化为货币持有者的地址。
- // TXInput 表示交易输入
- type TXInput struct {
- Txid []byte //引用的交易ID,表示交易的哈希
- Vout int //引用的输出索引
- ScriptSig string //解锁脚本
- }
在TXOutput
结构体中,ScriptPubKey
字段代表锁定脚本。它的作用是指定谁有权利花费这个输出中的资金。在真实的比特币系统中,ScriptPubKey
可以包含更复杂的脚本,但在这个简化模型中,它被设置为接收方的地址。
- // 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
- }
这种简化的实现方式有助于理解区块链交易的基本原理,即资产的转移需要通过正确的解锁条件。然而,在实际的区块链应用中,这种机制要复杂得多,涉及到密钥对和加密签名,以确保交易的安全性和用户的隐私。
- // -*- 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()
- }
这段代码实现了一个简单的命令行界面 (CLI),用于与区块链进行交互,包括创建区块链、查询余额、打印区块链和发送交易等功能。让我们逐一解析:
CLI
结构体用于处理命令行参数和执行相应操作。- // -*- 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)
- }
这段代码是整个区块链项目的入口,它创建了一个 CLI
对象,并调用其 run
方法启动命令行界面。
CLI
对象,用于处理命令行参数和执行相应操作。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对区块链数据进行持久化存储,这对于保证区块链数据的持久性和可靠性至关重要。
实现一个区块链系统虽然复杂,但通过逐步分析和实现,我们可以更好地理解其背后的原理和技术。希望这篇博客能为那些对区块链技术感兴趣的读者提供一定的帮助,并激发大家进一步探索和实践区块链技术的热情。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。