專案結構
main.go
package main
import "core"
func main(){
bc := core.NewBlockchain() //建立新的鏈條
defer bc.Db.Close() //main方法結束後才關db
cli := core.CLI{bc}
cli.Run()
}
block.go
package core
import (
"bytes"
"encoding/gob"
"log"
"time"
)
type Block struct{
Timestamp int64 // 區塊鏈建立時間戳
Data []byte //區塊包含的資料
PrevBlockHash []byte //前一個區塊的雜湊值
Hash []byte //區塊自身的雜湊值,用於校驗區塊資料有效
Nonce int //用於證明工作量
}
//Serialize()轉換區塊
func (b *Block) Serialize() []byte{
var result bytes.Buffer
encoder := gob.NewEncoder(&result) //gob可以把一個go語言當中的資料結構轉換成位元組陣列
err := encoder.Encode(b)
if err!=nil {
log.Panic(err)
}
return result.Bytes()
}
//NewBLock建立並返回一個區塊
func NewBlock(data string,prevBlockHash []byte) *Block{
//block := &Block{time.Now().Unix(),[]byte(data),prevBlockHash,[]byte{}}
block := &Block{time.Now().Unix(),[]byte(data),prevBlockHash,[]byte{},0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}
func NewGenesisBlock() *Block{
return NewBlock("Genesis Block",[]byte{})
}
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
}
blockchain.go
package core
import (
"fmt"
"log"
"github.com/boltdb/bolt" //特殊的資料儲存結構,用檔案來儲存
)
const dbFile = "blockchain.db"
const blocksBucket = "blocks"
//BlockChain內建一系列Blocks
type Blockchain struct{
tip []byte
Db *bolt.DB
}
//BlockchainIterator 用來遍歷 blockchain的blocks
type BlockChainIterator struct {
currentHash []byte
Db *bolt.DB
}
//AddBlock 把提供的資料儲存到區塊放進區塊鏈裡
func (bc *Blockchain)AddBlock(data string){
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(data, 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)
}
bc.tip = newBlock.Hash
return nil
})
}
//遍歷...
func (bc *Blockchain) Iterator() * BlockChainIterator{
bci := &BlockChainIterator{bc.tip,bc.Db}
return bci
}
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
}
//NewBlockchain創造一個新的創世區塊鏈
func NewBlockchain() *Blockchain {
var tip []byte
db, err := bolt.Open(dbFile,0600,nil) //開啟某一個硬碟上的檔案dbFile
if err !=nil { //如果開啟失敗則退出
log.Panic(err)
}
err = db.Update(func(tx *bolt.Tx)error{ //向資料庫裡更新資料
b := tx.Bucket([]byte(blocksBucket))
if b == nil { //第一次找是空的,判斷如果是空的則執行下面語句
fmt.Println(("No existing blockchain found. Creating a new one...")) //第一次是空的,找不到,輸出「建立一個吧」
genesis := NewGenesisBlock() //建立創世紀塊
b,err := tx.CreateBucket([]byte(blocksBucket)) //建立一個桶
if err !=nil { //如果建立失敗則退出
log.Panic(err)
}
err = b.Put(genesis.Hash,genesis.Serialize()) //put(key,value),其中key和value都是位元組陣列型別,genesis.Hash本區塊的雜湊值,genesis.Serialize()區塊產生的位元組陣列
if err != nil{
log.Panic(err)
}
err = b.Put([]byte("l"), genesis.Hash)//把雜湊放到key「l」中
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
}else{ //不是第一次建立時
tip = b.Get([]byte("l")) //把領頭的雜湊找出來就行
}
return nil
})
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip,db} //把區塊鏈返出來放進區塊鏈中
return &bc
}
cli.go
package core
import (
"flag"
"fmt"
"log"
"os"
"strconv"
)
type CLI struct {
Bc *Blockchain
}
//解析命令列的引數並執行命令
func (cli *CLI) printUsage(){
fmt.Println("Usage:")
fmt.Println(" addblock -data BLOCK_DATA - add a block to the blockchain")
fmt.Println(" printchain - print all the blocks of the blockchain")
}
func(cli *CLI) validateArgs(){
if len(os.Args)<2{ //如果給的命令字數不足顯示提示的引數printUsage()
cli.printUsage()
os.Exit(1)
}
}
func (cli *CLI) addBlock(data string){
cli.Bc.AddBlock(data)
fmt.Println("Success!")
}
func (cli *CLI) printChain() {
bci := cli.Bc.Iterator()
for{
block := bci.Next()
fmt.Printf("Prev. hash: %x\n",block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n",block.Hash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n",strconv.FormatBool(pow.Validate()))
if len(block.PrevBlockHash) == 0 {
break
}
}
}
//解析命令列引數並執行命令
func (cli *CLI) Run(){
cli.validateArgs()
addBlockCmd := flag.NewFlagSet("addblock",flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain",flag.ExitOnError)
addBlockData := addBlockCmd.String("data","","Block data")
switch os.Args[1] { //匹配輸入的命令,用第一個詞匹配,
case "addblock": //如果輸入的第一個詞是addblock,執行輸入的第二個詞
err := addBlockCmd.Parse(os.Args[2:])
if err !=nil {
log.Panic(err)
}
case "printchain"://如果輸入的第一個詞是printchain,則執行輸入的第二個詞
err := printChainCmd.Parse(os.Args[2:])
if err !=nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
}
if addBlockCmd.Parsed() {
if *addBlockData == "" {
addBlockCmd.Usage()
os.Exit(1)
}
cli.addBlock(*addBlockData) //第一個詞是addblock,如果輸入的值不是空值,就增加區塊
}
if printChainCmd.Parsed() {
cli.printChain() //第一個詞是printchain,如果輸入的值不是空值,就列印整條鏈
}
}
proofofwork.go
package core
import (
"bytes"
"crypto/sha256"
"fmt"
"math"
"math/big"
)
var (
maxNonce = math.MaxInt64
)
const targetBits = 8
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
}
func (pow *ProofOfWork) prepareData(nonce int) []byte{
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.Data,
IntToHex(pow.block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
},
[]byte{},
)
return data
}
//執行一個pow
func (pow *ProofOfWork) Run() (int,[]byte){
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining the block containing \"%s\"\n",pow.block.Data)
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[:]
}
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
}
utils.go
package core
import (
"bytes"
"encoding/binary"
"log"
)
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()
}
//func DataToHash(data []byte) []byte {
// hash :=sha256.Sum256(data)
// return hash[:]
//}
這次是把上次的工作量證明改改,由預設的語句改成由使用者輸入,改動最大的blockchain.go和cli.go,blockchain.go主要是改了區塊鏈的儲存方式,把以前的陣列改成了bucket(桶),用了bolt這個檔案?資料庫?就可以用key-value的方式儲存字元陣列了,然後cli.go主要負責處理使用者輸入。
而且printchain的結果很有意思,不是從創世區塊開始的,而是從最新的開始,大概像往桶裡放東西和取東西?
財富密碼
今天喝了炭燒,肚子就開始咕嘟嘟了,不虧是你,拉稀酸奶。蔫蔫的,要支愣起來呀狗醬!