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)