Learing blockchain in go

这篇文章描述了如何在Windows环境下配置JetBrainsGoLand,以及使用Go语言实现一个简易的区块链系统,包括区块、链、工作量证明(PoW)和交易处理。代码示例展示了交易验证、UTXO(未花费交易输出)的查找以及交易创建和挖矿过程。同时,文章提到了存在的问题,如双花问题和创世区块哈希的不匹配。
摘要由CSDN通过智能技术生成


 

 Windows下JetBrains GoLand环境配置记录


根据文末Reference 1 实现的迷你区块链,暂有 block、chain、pow、UTXO

现在实现的bc存在double spending问题,并且创世区块和创世交易的哈希与mian中测试样例 转出的address有出入,故交易不正确。

先保留个能运行的版本吧。

 

package blockchain

//block.go
import (
	"BlockChain/internal/transaction"
	"BlockChain/internal/utils"
	"bytes"
	"crypto/sha256"
	"encoding/gob"
	"time"
)

type Block struct {
	TimeStamp int64
	LocalHash []byte
	PrevHash  []byte
	DataField []byte
	//Pow needed
	TargetBits   []byte                     //目标难度值 puzzle friendly
	Nonce        int64                      //随机数
	Transactions []*transaction.Transaction //TxInfo
}

func (b *Block) ComputeHash() {
	info := bytes.Join([][]byte{
		utils.ToHexInt(b.TimeStamp),
		b.PrevHash,
		b.TargetBits,
		utils.ToHexInt(b.Nonce),
		b.BackTransactionSummary(),
	}, []byte{})
	hash := sha256.Sum256(info)
	b.LocalHash = hash[:]
}

func CreateBlock(previousHash []byte, data []byte, txs []*transaction.Transaction) *Block {
	block := Block{time.Now().Unix(), []byte{}, previousHash, data, []byte{}, 0, txs}
	block.TargetBits = block.SetTarget()
	block.Nonce = block.FindNonce()
	block.ComputeHash()
	return &block
}

// GenesisBlock 在创建创始区块的同时一并创建了创始交易信息
func GenesisBlock(address []byte) *Block {
	timeNow := time.Now()
	tx := transaction.BaseTx(address)
	genesisInfo := "Now you create the first Block, good luck!\n" + timeNow.String()

	genesis := CreateBlock([]byte{}, []byte(genesisInfo), []*transaction.Transaction{tx}) //创世区块的前一个哈希值为空
	genesis.ComputeHash()
	return genesis
}

func (b *Block) BackTransactionSummary() []byte {
	txIDs := make([][]byte, 0)
	for _, tx := range b.Transactions {
		txIDs = append(txIDs, tx.ID)
	}
	summary := bytes.Join(txIDs, []byte{})
	return summary
}

// Serialize Blocks to bytes for storing
func (b *Block) Serialize() []byte {
	var res bytes.Buffer
	encoder := gob.NewEncoder(&res)
	err := encoder.Encode(b)
	utils.Handle(err)
	return res.Bytes()
}

// DeSerializeBlock to Blocks for sql
func DeSerializeBlock(data []byte) *Block {
	var block Block
	decoder := gob.NewDecoder(bytes.NewReader(data))
	err := decoder.Decode(&block)
	utils.Handle(err)
	return &block
}

//replaced by ComputeHash()
//func (b *Block) SetHash() {
//	info := bytes.Join([][]byte{
//		utils.ToHexInt(b.TimeStamp),
//		b.PrevHash,
//		b.TargetBits,
//		utils.ToHexInt(b.Nonce),
//		b.BackTransactionSummary(),
//	}, []byte{})
//	hash := sha256.Sum256(info)
//	b.LocalHash = hash[:]
//}

package blockchain

//chain.go
import (
	"BlockChain/internal/transaction"
	"BlockChain/internal/utils"
	"encoding/hex"
	"fmt"
)

type BlockChain struct {
	//LastHash []byte //当前区块链最后一个区块的哈希值,不是必须的
	//Database *badger.DB
	Blocks []*Block
}

func (mainChain *BlockChain) AddBlock(data string, txs []*transaction.Transaction) *BlockChain {
	newBlock := CreateBlock(mainChain.Blocks[len(mainChain.Blocks)-1].LocalHash, []byte(data), txs)
	mainChain.Blocks = append(mainChain.Blocks, newBlock)
	return mainChain
}

func CreateBlockChain() *BlockChain {
	blockChain := BlockChain{}
	blockChain.Blocks = append(blockChain.Blocks, GenesisBlock([]byte{})) //注意,这里是" = "
	return &blockChain
}

func (bc *BlockChain) FindUnspentTransactions(address []byte) []transaction.Transaction {
	var unSpentTxs []transaction.Transaction
	spentTxs := make(map[string][]int)

	for idx := len(bc.Blocks) - 1; idx >= 0; idx-- {
		block := bc.Blocks[idx]
		for _, tx := range block.Transactions {
			txID := hex.EncodeToString(tx.ID)

		IterOutputs: //个人认为是个 goto loop的语句吧
			for outIdx, out := range tx.Outputs {
				if spentTxs[txID] != nil {
					for _, spentOut := range spentTxs[txID] {
						if spentOut == outIdx {
							continue IterOutputs
						}
					}
				}
				if out.ToAddressRight(address) {
					unSpentTxs = append(unSpentTxs, *tx)
				}
			} //for
			if !tx.IsBase() {
				for _, in := range tx.Inputs {
					if in.FromAddressRight(address) {
						inTxID := hex.EncodeToString(in.TxID)
						spentTxs[inTxID] = append(spentTxs[inTxID], in.OutIdx)
					}
				}
			}
		}
	}
	return unSpentTxs
}

func (bc *BlockChain) FindUTXOs(address []byte) (int, map[string]int) {
	unspentOuts := make(map[string]int)
	unspentTxs := bc.FindUnspentTransactions(address)
	accumulated := 0

Work:
	for _, tx := range unspentTxs {
		txID := hex.EncodeToString(tx.ID)
		for outIdx, out := range tx.Outputs {
			if out.ToAddressRight(address) {
				accumulated += out.Value
				unspentOuts[txID] = outIdx
				continue Work
			}
		}
	}

	return accumulated, unspentOuts
}

// FindSpendableOutputs 寻找大于消费的UTXO
func (bc *BlockChain) FindSpendableOutputs(address []byte, amount int) (int, map[string]int) {
	unspentOuts := make(map[string]int)
	unspentTxs := bc.FindUnspentTransactions(address)
	accumulated := 0

Work:
	for _, tx := range unspentTxs {
		txID := hex.EncodeToString(tx.ID)
		for outIdx, out := range tx.Outputs {
			if out.ToAddressRight(address) && accumulated < amount {
				accumulated += out.Value
				unspentOuts[txID] = outIdx
				if accumulated >= amount {
					break Work
				}
				continue Work
			}
		}
	}
	return accumulated, unspentOuts
}

func (bc *BlockChain) CreateTransaction(from, to []byte, amount int) (*transaction.Transaction, bool) {
	var inputs []transaction.TxInput
	var outputs []transaction.TxOutput

	acc, validOutputs := bc.FindSpendableOutputs(from, amount)

	//token不足
	if acc < amount {
		fmt.Println("go ming! token are not enough.")
		return &transaction.Transaction{}, false
	}

	for txId, outIdx := range validOutputs {
		IxID, err := hex.DecodeString(txId)
		utils.Handle(err)
		input := transaction.TxInput{TxID: IxID, OutIdx: outIdx, FromAddress: from}
		inputs = append(inputs, input)
	}

	outputs = append(outputs, transaction.TxOutput{
		Value: acc - amount, ToAddress: to},
	)

	if acc > amount {
		outputs = append(outputs, transaction.TxOutput{
			Value: acc - amount, ToAddress: from})
	}

	tx := transaction.Transaction{
		ID:      nil,
		Inputs:  inputs,
		Outputs: outputs,
	}

	tx.SetID()

	return &tx, true
}

func (bc *BlockChain) Mine(txs []*transaction.Transaction) {
	data := "This is the Mine Block"
	bc.AddBlock(data, txs)
}

package blockchain

//pow.go
import (
	"BlockChain/internal/constcoe"
	"BlockChain/internal/utils"
	"bytes"
	"crypto/sha256"
	"math"
	"math/big"
)

func (b *Block) SetTarget() []byte {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-constcoe.Difficulty)) //将传入的第一个参数左移右边的位数            (Locality Sensitive Hashing)
	return target.Bytes()
}

func (b *Block) SetBase4Nonce(nonce int64) []byte {
	data := bytes.Join([][]byte{
		utils.ToHexInt(b.TimeStamp),
		b.PrevHash,
		utils.ToHexInt(nonce),
		b.TargetBits,
		b.DataField,
		b.BackTransactionSummary(),
	},
		[]byte{},
	)
	return data
}

func (b *Block) FindNonce() int64 {
	var intHash big.Int
	var intTarget big.Int
	var hash [32]byte
	var nonce int64

	nonce = 0 //init
	intTarget.SetBytes(b.TargetBits)

	//终止条件1:nonce的尝试次数超出64位int最大值;2:找到符合目标难度的nonce
	for nonce < math.MaxInt64 {
		data := b.SetBase4Nonce(nonce)
		hash = sha256.Sum256(data)
		intHash.SetBytes(hash[:])
		if intHash.Cmp(&intTarget) == -1 { //intHash小于intTarget
			break
		} else {
			nonce++
		}
	}
	return nonce
}

// ValidatePow 校验Hash是否符合挖矿难度
func (b *Block) ValidatePow() bool {
	var intHash big.Int
	var intTarget big.Int
	var hash [32]byte

	intTarget.SetBytes(b.TargetBits)
	data := b.SetBase4Nonce(b.Nonce)
	hash = sha256.Sum256(data)
	intHash.SetBytes(hash[:])

	if intHash.Cmp(&intTarget) == -1 {
		return true
	} else {
		return false
	}
}

package constcoe

//constcoe.go

//存放全局常量

const (
	Difficulty          = 20  //离散对数问题难度,对应block中的target
	InitCoin            = 200 // The init of bitcoins
	TransactionPoolFile = "BlockChain/tmp/transaction_pool.data"
	BCPath              = "BlockChain/tmp/blocks"
	//TransactionPoolFile = "./tmp/transaction_pool.data" //this line is new
	//BCPath              = "./tmp/blocks"                //this line is new
	//BCFile              = "./tmp/blocks/MANIFEST"       //this line is new

)


package transaction

//inoutput.go

import "bytes"

type TxInput struct {
	TxID        []byte //支持本次交易的前置交易ID
	OutIdx      int    //前置交易信息中的Output序号
	FromAddress []byte //转出者的地址
}

type TxOutput struct {
	Value     int    //output value  转出的资产值
	ToAddress []byte //receiver address   接收者的地址
}

func (in *TxInput) FromAddressRight(address []byte) bool {
	return bytes.Equal(in.FromAddress, address)
}

func (out *TxOutput) ToAddressRight(address []byte) bool {
	return bytes.Equal(out.ToAddress, address)
}

package transaction

//transaction.go

import (
	"BlockChain/internal/constcoe"
	"BlockChain/internal/utils"
	"bytes"
	"crypto/sha256"
	"encoding/gob"
)

type Transaction struct {
	ID      []byte //Hash val of tx itself
	Inputs  []TxInput
	Outputs []TxOutput
}

func (tx *Transaction) TxHash() []byte {
	var encoded bytes.Buffer
	var hash [32]byte

	encoder := gob.NewEncoder(&encoded) //序列化结构体 like json
	err := encoder.Encode(tx)
	utils.Handle(err)

	hash = sha256.Sum256(encoded.Bytes())
	return hash[:] //return the transaction's hash
}

// SetID 设置交易的哈希标识
func (tx *Transaction) SetID() {
	tx.ID = tx.TxHash() //一笔交易的ID为其自身的哈希
}

func BaseTx(toAddress []byte) *Transaction {
	//创始交易信息因为是凭空产生的,其Input指向一个为空的交易信息中的序号为-1的Output
	txIn := TxInput{
		[]byte{},
		-1,
		[]byte{},
	}

	//创始交易信息的Output不为空,暂时指向一个地址
	txOut := TxOutput{
		constcoe.InitCoin, //区块链在创建时的比特币总数
		toAddress,
	}

	tx := Transaction{
		[]byte("This is the Base Transaction!"),
		[]TxInput{txIn},
		[]TxOutput{txOut},
	}

	tx.SetID()

	return &tx
}

// IsBase 用于检验一个交易信息是否为创始交易信息的
func (tx *Transaction) IsBase() bool {
	return len(tx.Inputs) == 1 && tx.Inputs[0].OutIdx == -1
}


package utils

//utils.go

import (
	"bytes"
	"encoding/binary"
	"log"
	"os"
)

//存放一些通用方法

// ToHexInt 返回64位int类型数据的字节串
func ToHexInt(num int64) []byte {
	buff := new(bytes.Buffer)
	err := binary.Write(buff, binary.BigEndian, num)
	if err != nil {
		log.Panic(err) //对于log.Panic接口,该函数把日志内容刷到标准错误后输出
	}
	return buff.Bytes()
}

// Handle print the err msg
func Handle(err error) {
	if err != nil {
		log.Panic(err)
	}
}

// FileExists 检查文件地址下文件是否存在的函数
func FileExists(fileAddr string) bool {
	if _, err := os.Stat(fileAddr); os.IsNotExist(err) {
		return false
	}
	return true
}

// try to make this file simple
// Reference:https://www.krad.top/goblockchain01/
package main

//main.go

//按照包名首字母字典序导入需要使用到的包
import (
	"BlockChain/internal/blockchain"
	"BlockChain/internal/transaction"
	"fmt"
)

func main() {
	fmt.Println("\nGo everywhere just you wanner!")

	//blockChain := blockchain.CreateBlockChain() //blockchain为包名,CreateBlockChain()为该目录下的函数
	//blockChain.AddBlock("After genesis, I was the second Block.")
	//time.Sleep(time.Second)
	//blockChain.AddBlock("BlockChain is awesome!")
	//time.Sleep(time.Second)
	//blockChain.AddBlock("I can't wait to follow this!")
	//time.Sleep(time.Second)

	txPool := make([]*transaction.Transaction, 0)
	var tempTx *transaction.Transaction
	var ok bool
	var property int
	chain := blockchain.CreateBlockChain()
	property, _ = chain.FindUTXOs([]byte("Happy"))
	fmt.Println("Balance of Happy: ", property)

	tempTx, ok = chain.CreateTransaction([]byte("Happy"), []byte("Krad"), 100)
	if ok {
		txPool = append(txPool, tempTx)
	}
	chain.Mine(txPool)

	txPool = make([]*transaction.Transaction, 0)

	property, _ = chain.FindUTXOs([]byte("Happy"))
	fmt.Println("Balance of Happy: ", property)

	property, _ = chain.FindUTXOs([]byte("Krad"))
	fmt.Println("Balance of Krad: ", property)

	tempTx, ok = chain.CreateTransaction([]byte("Krad"), []byte("Exia"), 200) // this transaction is invalid
	if ok {
		txPool = append(txPool, tempTx)
	}

	tempTx, ok = chain.CreateTransaction([]byte("Krad"), []byte("Exia"), 50)
	if ok {
		txPool = append(txPool, tempTx)
	}

	tempTx, ok = chain.CreateTransaction([]byte("Happy"), []byte("Exia"), 100)
	if ok {
		txPool = append(txPool, tempTx)
	}
	chain.Mine(txPool)

	property, _ = chain.FindUTXOs([]byte("Happy"))
	fmt.Println("Balance of Happy: ", property)
	property, _ = chain.FindUTXOs([]byte("Krad"))
	fmt.Println("Balance of Krad: ", property)
	property, _ = chain.FindUTXOs([]byte("Exia"))
	fmt.Println("Balance of Exia: ", property)

	chain.Mine(txPool)
	txPool = make([]*transaction.Transaction, 0)
	property, _ = chain.FindUTXOs([]byte("Happy"))
	fmt.Println("Balance of Happy: ", property)
	property, _ = chain.FindUTXOs([]byte("Krad"))
	fmt.Println("Balance of Krad: ", property)
	property, _ = chain.FindUTXOs([]byte("Exia"))
	fmt.Println("Balance of Exia: ", property)

	for _, block := range chain.Blocks {
		fmt.Printf("Timestamp: %d\n", block.TimeStamp)
		fmt.Printf("LocalHash: %x\n", block.LocalHash)
		fmt.Printf("Previous hash: %x\n", block.PrevHash)
		fmt.Printf("data: %s\n", block.DataField)
		fmt.Printf("TargetBits:%d\n", block.TargetBits)
		fmt.Printf("Nonce:%d\n", block.Nonce)
		fmt.Println("PoW validated?", block.ValidatePow())

	}

	//I want to show the bug at this version.

	tempTx, ok = chain.CreateTransaction([]byte("Krad"), []byte("Exia"), 30)
	if ok {
		txPool = append(txPool, tempTx)
	}

	tempTx, ok = chain.CreateTransaction([]byte("Krad"), []byte("Happy"), 30)
	if ok {
		txPool = append(txPool, tempTx)
	}

	chain.Mine(txPool)
	txPool = make([]*transaction.Transaction, 0)

	for _, block := range chain.Blocks {
		fmt.Printf("Timestamp: %d\n", block.TimeStamp)
		fmt.Printf("LocalHash: %x\n", block.LocalHash)
		fmt.Printf("Previous hash: %x\n", block.PrevHash)
		fmt.Printf("data: %s\n", block.DataField)
		fmt.Printf("TargetBits:%d\n", block.TargetBits)
		fmt.Printf("Nonce:%d\n", block.Nonce)
		fmt.Println("PoW validated?", block.ValidatePow())

	}

	property, _ = chain.FindUTXOs([]byte("Happy"))
	fmt.Println("Balance of Happy: ", property)
	property, _ = chain.FindUTXOs([]byte("Krad"))
	fmt.Println("Balance of Krad: ", property)
	property, _ = chain.FindUTXOs([]byte("Exia"))
	fmt.Println("Balance of Exia: ", property)

}

下载Badger 数据库 ,官网:https://github.com/dgraph-io/badger

项目目录下使用cmd输入:

go env -w GOPROXY=https://goproxy.cn
go get github.com/dgraph-io/badger

运行结果:

 

 


更改创世区块以及创世交易后的出错版本:

 

References:

深入底层:Go语言从零构建区块链(四):区块链的存储、读取与管理 - Mingrui Cao's Blog (krad.top)

解决golang编译提示dial tcp 172.217.160.113:443: connectex: A connection attempt failed_白叔King的博客-CSDN博客_172.217.160.113 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七月是你的谎言..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值