轉載請註明出處:http://www.javashuo.com/article/p-sfaggmbx-en.htmlhtml
人們能夠用許多不一樣的方式解釋區塊鏈技術,其中經過加密貨幣來看區塊鏈一直是主流。大多數人接觸區塊鏈技術都是從比特幣談起,但比特幣僅僅是衆多加密貨幣的一種。python
到底什麼是區塊鏈技術?程序員
從金融學相關角度來看,區塊鏈是一種存儲數據的方式,去中心化的數據庫,應用到比特幣也就是去中心化帳本;算法
從密碼學角度來看,區塊鏈是一種傳遞價值的協議;數據庫
從計算機科學的角度來看,區塊鏈只是一種數據結構;編程
不一樣於咱們平時接觸的手機電腦,先有系統,而後纔會在系統裏開發各類APP應用。09年第一枚比特幣誕生,15年也就是6年以後,纔有區塊鏈這個概念。許多人瞭解區塊鏈,都是從金融學或者密碼學的角度做爲切入,從中本聰的比特幣白皮書開始談起。經過金融角度看待區塊鏈,總有種霧裏看花的感受,從密碼學角度看區塊鏈,分析粒度又太細了。就計算機而言,咱們所須要的只是看到這項技術的本質。當技術與金融一旦掛鉤,每每就會變成玄學,區塊鏈也是這樣,當這項技術概念被從比特幣中抽離出來的時候,比特幣就只不過是這項技術的一個Demo而已。數組
接下來拋開金融學的概念,密碼學的理論,不關心區塊鏈金融,不研究區塊鏈安全,只分析區塊鏈技術,從計算機科學來了解區塊鏈的模型。安全
下面將經過Go語言,來編碼實現一個簡易的區塊鏈模型,模型分爲不一樣階段,本文先實現區塊鏈簡易的數據結構、工做量證實共識、數據庫持久化存儲以及命令行接口。數據結構
即便沒學過Go語言,也能夠馬上上手。Go語言語法與python相似,卻又是編譯型而非解釋型語言,有着媲美C的高性能,是區塊鏈開發主流語言。架構
對於程序員而言,須要用邏輯解釋的問題,經過代碼結合語言特性來描述是最簡單易懂的。實際上,當你讀完這篇文章,不僅是Go語言,你能夠用你熟悉的其餘語言實現一樣的效果,由於這只是個簡簡單單的數據結構。從學習角度來講,都是C類語法,因此你只須要看懂,不須要會寫,就能夠轉換到你熟悉的語言實現。
接下來的代碼,是在多個文件中實現的,這裏有必要先簡單討論下Go語言的一些語言特性,這有助於後續的代碼邏輯理解。
"一個程序就是一個世界,有許多不一樣的對象",從C語言不徹底面向對象,到Java的面向對象,再到Go語言的不純粹面向對象,都與現實世界中抽離出的對象這個概念緊密相連。Go語言其實是沒有對象的面向對象編程,由於從語法角度上來講她沒有」類「。最吸引人的不是Go擁有的特徵,而是那些被故意遺漏的特徵。
爲何你要創造一種從理論上來講,並不使人興奮的語言?
由於它很是有用。 —— Rob Pike
C語言沒有徹底的面向對象,她在這方面沒有徹底的語法約束,然後來的Java作了這種約束,到現在的Golang去除了這些約束。從語言自己角度,這並不讓人興奮,但確實很是好用。
「若是你能夠從新作一次Java,你會改變什麼?」
「我會去掉類class,」 他回答道。
在笑聲消失後,他解釋道,真正的問題不是類class自己,而是「實現」的繼承(類之間extends的關係)。接口的繼承(implements的關係)是更可取的方式。
只要有可能,你就應該儘量避免「實現」的繼承。
—— James Gosling(Java之父)
Go能夠包含對象的基本功能:標識、屬性和特性,因此是基於對象的。若是一種語言是基於對象的,而且具備多態性和繼承性,那麼它被認爲是面向對象的。
那麼Go語言是如何實現繼承的?
Go經過嚴格地遵循了符合繼承原則的組合方式,明確地避免了繼承,她使用嵌入類型來實現組合。
繼承把「知識」向下傳遞,組合把「知識」向上拉昇
—— Steve Francia
Go語言又如何實現多態?
Go明確避免了子類型和重載,還沒有提供泛型,利用接口提供了多態功能。
總而言之,Go用盡量少的語法規則,實現了儘量多的語言特性。這使得go語言面向對象很是簡潔,去掉了傳統OOP語言的繼承,方法重載,構造函數和析構函數等等。
咱們以後的編碼,分多個文件完成的緣由也正是由於這些,咱們將感受像對象的多個文件看成對象,只要它走起路來像鴨子,叫起來像鴨子,那麼咱們就認爲它是一隻鴨子。(實際上,一些文件中用到了Go語言提供的面向對象特性,但這裏不作詳細解釋區分)
這是咱們要編寫的main.go文件:
package main func main() { bc := NewBlockchain() defer bc.db.Close() cli := CLI{bc} cli.Run() }
咱們須要編寫的還有
block.go、 blockchain.go、 cli.go、 proofofwork.go、 utils.go
每部分代碼都很簡短,分文件是爲了經過儘可能簡單的方式作出咱們想看到的對象。
你能夠把這些文件當作一個個」類「,也能夠把這些文件當作某些業務邏輯關係,由於Go自己沒有作出限制你不能這麼用,你能夠按照你感受像的邏輯去理解。
那麼咱們究竟要寫的是什麼樣的結構?
你能夠把咱們的任務理解成,先構造一種結點,而後再將這種結點連接成鏈表,只不過鏈表結點的加入須要知足一種特殊條件——工做量證實共識機制(只不過是暴力搜索湊出一個特定條件的字符串而已,讓你不能隨隨便便往鏈表添加數據,美其名曰工做量證實,跟計算機算力較勁...所謂的挖礦),最後咱們須要把完成的鏈表保存到數據庫,本節所要討論的簡易區塊鏈從編碼角度來看只是這麼個簡單結構而已。從非編碼角度去分析區塊鏈,每每就複雜了,可能還不必,由於那些不是你想理解的。
實現順序爲:
你能夠理解成是自底向上的順序,也能夠理解成某種業務依賴關係的順序,能夠自由的用你熟悉的方式去理解。
由於咱們要用這個go語言沒提供的類型轉換,因此有了untils.go
package main import ( "bytes" "encoding/binary" "log" ) // IntToHex converts an int64 to a byte array 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() }
假設如今有了一個區塊鏈,你們都能隨便往區塊鏈上寫入本身的塊,那麼這個區塊鏈就沒什麼價值可言。因此須要設置一個門檻,當你知足必定條件時,才容許你往區塊鏈上添加新的數據,這樣付出了成本纔有了價值。而區塊鏈的門檻,就是一個被叫作工做量證實共識的東西,這是一個全部礦工(想得到添加區塊資格的人)都認同的門檻。
好比咱們當前實現的簡易區塊鏈,考慮到執行時間問題,咱們的門檻就是找到一串長度爲64的字符串,而這串字符串的前6位爲0.
這個字符串不是隨便構造的,否則能夠直接指定前6位爲0,後面都爲隨機的字符串,那樣就沒有價值可言了,因此咱們還須要一個構造的規則。
怎麼讓找到某種字串具備工做難度?容易想到的是尋找哈希值。
比如你如今知道了一個用戶密碼的哈希值,想去找出用戶密碼,區塊鏈所謂的工做量證實、挖礦,都只是去找一個你們共識認可的哈希值而已(...與計算機算力鬥智鬥勇,手動滑稽)。
咱們本節用到的字符串獲取規則,是採用sha256算法。關於sha256算法的具體原理,或者爲何選擇該哈希算法,不是咱們當前要考慮的問題,對密碼學有興趣的能夠自行拓展瞭解。要進行哈希運算的字符串,也有必定的共識規則:
字符串 = 前一個區塊的哈希值 + 當前塊數據 + 時間戳 + targetBits(後面會解釋)+ 自由參數。
實際挖礦咱們只是想找到有價值的自由參數,其他值都是給定的。
瞭解完這些概念,接下來就能夠直接看代碼了:
package main import ( "bytes" "crypto/sha256" "fmt" "math" "math/big" ) var ( maxNonce = math.MaxInt64 ) const targetBits = 24 // ProofOfWork represents a proof-of-work type ProofOfWork struct { block *Block target *big.Int } // NewProofOfWork builds and returns a 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 } // Run performs a proof-of-work 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("\n%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } // fmt.Print("\n\n") return nonce, hash[:] } // Validate validates block's PoW 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 }
關於全局變量targetBits,是用作規定當前的「共識」,24位表明哈希值前24位爲0,也就是6個十六進制的0,纔算有效哈希值(礦)。
咱們定義了一個proofofwork的結構體,包含兩個指針,一個指向區塊,一個指向閾值。
關於閾值,咱們能夠理解成,既然須要找到一個哈希值前24位爲0,那麼把它看成二進制數字看的話,一個第23位爲1,其他位爲0的二進制數就是咱們要找的哈希值上限(閾值)。只要咱們找到的哈希值小於這個數,那麼該哈希值的前24位確定爲0.
除此以外,
函數都很好理解,簡單到只須要看懂語法,不須要過多解釋邏輯,這就是最本質的區塊鏈。
主要包含如下內容:
package main import ( "bytes" "encoding/gob" "log" "time" ) // Block keeps block headers type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int } // NewBlock creates and returns Block func NewBlock(data string, prevBlockHash []byte) *Block { 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 } // NewGenesisBlock creates and returns genesis Block func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{}) } // Serialize serializes the block 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() } // DeserializeBlock deserializes a block 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 }
這裏要引入數據庫,用來存儲咱們當前的區塊鏈,不使用數據庫也能夠,但那樣每次都須要從新運行查看,沒法持久化。
本節使用到的boltdb是go實現的一個k-v數據庫。
package main import ( "fmt" "log" "bolt-master" ) const dbFile = "blockchain.db" const blocksBucket = "blocks" // Blockchain keeps a sequence of Blocks type Blockchain struct { tip []byte db *bolt.DB } // BlockchainIterator is used to iterate over blockchain blocks type BlockchainIterator struct { currentHash []byte db *bolt.DB } // AddBlock saves provided data as a block in the blockchain 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 }) } // Iterator ... func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci } // Next returns next block starting from the tip 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 creates a new Blockchain with genesis Block func NewBlockchain() *Blockchain { 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)) 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()) if err != nil { log.Panic(err) } err = b.Put([]byte("l"), genesis.Hash) 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 }
blockchain文件的實現:
總之,blockchain只是將區塊鏈存入了數據庫,有向數據庫初始化一個新區塊鏈與增長區塊的功能。
cli.go只是方便了在命令行下運行這些代碼
package main import ( "flag" "fmt" "log" "os" "strconv" ) // CLI responsible for processing command line arguments 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 { 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())) fmt.Println() if len(block.PrevBlockHash) == 0 { break } } } // Run parses command line arguments and processes commands 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": err := addBlockCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "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) } if printChainCmd.Parsed() { cli.printChain() } }
主要包括:
main函數入口
package main func main() { bc := NewBlockchain() defer bc.db.Close() cli := CLI{bc} cli.Run() }
"Pay 0.0013 BTC for a coffee" 挖礦的時間比"Send 1 BTC to Tom"多了一個數量級,程序根據你的輸入數據,計算哈希所須要的時間是不肯定的,感興趣的能夠記錄挖礦時間而後輸出。(不要打印挖礦過程,io輸出會讓處理時間多出好幾個量級,會覺得是無限循環)
本節實現了區塊鏈的簡易數據結構、工做量證實機制、持久化以及命令行接口。實際上核心只有proofofwork.go的工做量證實與block.go的區塊鏈結構,所謂挖礦也只是找一個有價值的哈希值而已。但實際的區塊鏈不只僅只是這些,以後將會結合比特幣實現交易與地址機制。之因此結合比特幣實現,是由於比特幣是區塊鏈技術的一個成功Demo,並非炒做比特幣概念。再好的概念,與現有法律和監管體系不兼容,也沒法成爲主流。作個不恰當的類比,有點像C#與JAVA,就語言自己C#比JAVA更先進,但一涉及生態又是另外一回事了。