區塊鏈難理解?200行代碼教你寫一個自己的區塊鏈!
“區塊鏈”三個字,無疑是近一年來最火的投資概念。隨著比特幣等區塊鏈資產價格的大幅飆升,普通投資者對區塊鏈,以及數字貨幣投資的興趣越來越大,突然間,似乎全世界都在談論區塊鏈、比特幣。
區塊鏈就像一次對全人類經濟層面的入侵,這種入侵,比互聯網的入侵,可能還要徹底,人們開始轉移的,并不是簡單的信息以及消費習慣,而是對資產的重新認識和選擇。
區塊鏈難理解?這里有一篇初學者指南
我并不明白為什么人們會覺得要理解區塊鏈會有點難,而我也想知道為什么自己并沒有。
那是在 2013 年,我第一次聽說有比特幣這個東西(是的,知道的太晚啦)。我窮得連一個都買不起,看到這兒你也許已經對此文無愛了。
后來,我想要去了解一下它所依賴的底層技術,也就是區塊鏈。 不過因為太忙了,所以遲遲沒有開始(那就并不存在什么開始不開始了)。
“區塊鏈”到底是什么?
區塊鏈其實是兩個東西:一個是區塊,一個是鏈。說玄虛一點,就是一鏈子的區塊。
因為它是存在于計算機中的東西,所以我們可以厘定它的一個物理形態是啥樣子的,就是數字信息被分成一個一個區塊然后把這些區塊鏈接起來。
舉個例子,下圖中的方塊,每一個都表示一個國家,而且每一個都包含了對應國家的城市名稱。
等等,其實還有更多東西來著。這里的每一個方塊都是一個叫做哈希的東西。一個哈希就是一串字符 (比如 “1hi515AHA5H” )。
哈希是根據方塊里面所包含的信息來得到的。U.S.A 的方塊擁有 New York, Los Angeles,還有 Chicago 這幾個城市,所以它的哈希就是像 “NYLAC” 的東西了 (技術上其實遠非如此,但你理會精要就行了)。
每一個接續的方塊都會包含前一個方塊的哈希,所以這個就是(強制性的)將它們綁到一起的紐帶。
如果有人擅自篡改了第一個方塊,加入了城市 Boston,那么新的哈希就會是 “NYLACB”。
然而后面接續的 India 這個方塊已經存著的哈希還是 “NYLAC”,這種不匹配就會把鏈條打斷。所以哈希的目的就是確保沒有人可以篡改區塊。
那如果有人修改了一個方塊的內容,然后把后面的接續方塊的哈希也一并更新會如何呢?
這也是有可能的,不過有一件事情我還沒有告訴你。區塊鏈的數據并不只是存在于僅僅一臺計算機里面。一臺計算機里面的區塊鏈數據并不能騙到人,因為它會被復制到網絡中每一個用戶的計算機里面去。
如果你加入了一個區塊鏈網絡,那么你的計算機就會去下載這些區塊數據,如果有人篡改了他擁有的版本,整個網絡也會考慮占多數的人的計算機上所擁有的版本才是正確的。
還有一件事,在一個區塊鏈網絡中,不僅是數據,就連整個系統的程序都被復制到了所有的電腦中。
大多數互聯網應用都是集中化的,比如 Facebook,她的數據和程序都被放在了她的服務器上,你的計算機會從 Facebook 的服務器上獲取到你一個人需要知道的信息。
但在區塊鏈的世界理,就沒有存在于中心的東西,它依賴的是用戶的計算機來容納自己的程序。這就意味著,如果整個區塊鏈網絡中的每一臺電腦都關機了,那么這個區塊鏈系統就死翹翹了。
公共區塊鏈
這是不是就意味著區塊鏈系統其實就是由一群心懷善意自愿讓他們的計算機保持運行的人來組成的呢? 還有這些防篡改的區塊是用來干嘛的呢?
區塊鏈網絡的功效不勝枚舉。比特幣是一種數據貨幣和一個支付系統。它所有的防篡改區塊中所保存的就是全部交易的分類賬。那些貢獻了他們自己的計算機的人被稱為礦工。系統會給他們提供比特幣作為獎勵。
Ethereum 有一項附加功能。它可以承載你的代碼,從頭開始發展出一個區塊鏈系統。
而要構建一個屬于你自己的系統也許會非常地困難(記住這得看有人為你犧牲他們的計算機運行能力才行哦)。Ethereum 就維護著這些耗損巨大的運算能力,而你則需要為這些計算消耗買單。
區塊鏈應用并不非得是支付系統或者加密貨幣。它可以是任何東西,像是一個社交網絡,一個像 LiveEdu 這樣的學習平臺,等等。
私有區塊鏈
Bitcoin,Ethereum 等等這些都是公共區塊鏈的例子,任何人都可以成為其中的一分子。
那如果我們想要有一個私有的區塊鏈網絡該如何呢?有些人想要一個私有的區塊鏈是想干嘛呢?那就來瞧瞧下面的故事吧。
Mark 和 Sara
Mark 已經五個月沒交房租了,當 Sara 找他要的時候,他就說晚點會給她。她付不起律師費,而法院強制執行訴訟就需要 8 個月甚至一年,所以唯一的選擇就是去說服 Mark。
Joe 的生意
Joe 是一個商人,他經常要跟不同的公司做生意。幾個月之前他和一家零售商簽了一份合同,盡管合同條款都已經履約了,可零售商卻拒絕付款。
這幫人利用法律制度中的漏洞來游說 Joe,想以此達到少付錢的目的。Joe 在這以前就是有這方面經驗的,在某些情況下,他會找法院求助,但這樣做所耗費的時間和金錢卻要損失他自己的利潤。
我們該如何幫助 Sara 和 Joe 呢?
我們是不是能在其他地方解決這個問題呢? 在 Sara 遇到的這種情況中,我們需要讓 Mark 按月支付房租,這其實就是一個基于時間的觸發機制。你的日歷程序使用這樣的觸發器來給你提供預設事件的通知。
在 Joe 遇到的場景中,一旦合約中的條款都滿足了,當事人就得付款,這其實就是一個基于條件的觸發機制。你想想上次從 Amazon 買電子書的時候,是不是得先確認付款了,Amazon 才會把電子書發給你?
重點是,計算機程序會始終如一的執行諸如此類的指令。當你點擊著這篇文章,向下滾動,諸如這類的操作,它也會照著執行不誤。為了能幫助到 Sara ,我們需要將合同的條款轉變成代碼。
Sara 和 Mark 之間所訂立的智能合同的偽代碼
- If today’s date is 30th and rent is not paid then
- Transfer $500 from Mark’s account to Sara’s account
可是我們在哪兒部署這些代碼呢? 它就應該被部署到所有參與者的計算機上。Sara 的還有 Mark 的銀行都會是這一個私有區塊鏈網絡的一部分。
Joe 和 Sara 會簽署一份編碼的協議(也就是智能合同),然后這份協議會被分發到網絡中去,Mark 的和 Sara 的銀行都會有一份拷貝。
在每個月的 30 號,當時鐘跳動到 12 點整,協議好的金額就會從 Mark 的賬戶轉移到 Sara 的賬戶上去。Joe 也開始使用智能合同來強制讓他的客戶支付協議好的貨款。
- Sara 高興了,因為她再也不用去煩心 Mark 會不會如約付房租了。Joe 也高興,因為他也不用找法院要說法了,省下這些精力,他可以繼續發展自己的生意了。
私有區塊鏈只限于業務中涉及到的相關各方,因此 Joe 不會是 Sara 和 Mark 所屬區塊鏈網絡的一部分。
前行之路
現在你對此是不是已經有點概念了?如果還是不理解,看看小編之前發布的文章技術人再不懂區塊鏈,你就OUT了?不過下面這篇文章也能讓你秒懂區塊鏈。
區塊鏈與裸照:一個去中心化的色情網站是什么樣的?
“區塊鏈”概念已火,雖然大部分人對“區塊鏈”好奇,甚至眼饞,但不少還處于不求甚解的懵逼階段.....正好最近我一直在研究區塊鏈,同時也見了幾個圈內人深聊了下,就想為大家寫一個“入門級”的區塊鏈介紹文章。
為了通俗易懂,我決定不惜自毀清譽,用充滿荷爾蒙的比喻。因為技浪潮每次確實都性感得讓人荷爾蒙爆炸啊,性(huang)感(bao)內容開始。
以前,大家想看陳老師的裸照,都要去一個叫 1025 的網站,這就是中心化。
后來,1025 網站被和諧掉了,大部分猥瑣男們傻逼了沒有網站看片了,因為他們太相信中心化組織了,還天天被 1025 彈窗“皇家澳門賭場”的小廣告真是活該啊。
不過沒關系,陳老師的 2100 張裸照,幸存在 100 萬個猥瑣男的電腦里,除非地球毀滅,不然陳老師的裸照不可能絕跡。這就是去中心化,數據分布式存儲。
后來,有個叫“中本粗”的超級猥瑣男,是陳老師 2100 張裸照的超級發騷友。為了2100張照片永遠不消失,為世人所享用,他做了一個互聯網共享文件夾“陳老師plus”。
如果猥瑣男們想獲取“陳老師plus”2100 張的觀看權,就必須加入一個電子協議中:不得復制、修改、P 任何“陳老師plus”中的照片,用戶在“陳老師plus”發生的任何行為,都會按時間戳記錄!
例如,“小張在 2018 年 1 月 9 日中午 12:00,查看了編號為 103 的照片,并在 13:00 刪除了編號 1-100 的 100 張照片.....”
小張的行為被記錄并廣播給其他 100 萬個猥瑣男,“陳老師plus”的 2100 張照片會得到保護,小張電腦中“陳老師plus”會按時間戳中最新記錄,同步其他 100 萬個猥瑣男的電腦里的數據,復原小張電腦中的數據...
小張永遠別想對“陳老師plus”搞修改破壞,且所有行為都同步記錄在其他猥瑣男的電腦里。
這就是區塊鏈,數據分散存儲,去中心化,按時間戳廣播記錄所有行為,無法修改、破壞數據源或造假,除非同一時刻炸掉 100 萬個猥瑣男的電腦,或互聯網消失,或世界毀滅.....
當然,也有唯一一種特殊情況,可以增加“陳老師plus”文件夾中的照片,這種情況叫做“區塊鏈共識層”,顧名思義,這是 100 萬猥瑣男達成的增加照片共識,你不能瞎增加的。
中本粗最初設定協議時,把“猥瑣男們可以用 X 相機,在每年 XX 時間,拍陳老師的裸照,前 100 張可以添加進入‘陳老師plus’中增加作為文件夾照片”,那么,“陳老師plus”每年就可以增長 100 張照片了。
當然,你還可以給照片估價嘛,發行“陳老師plus”幣。因為,“陳老師plus”中每張照片都是不可造假破壞的,所以具有唯一性,還有單獨編號,我們就給每一張照片估價,它不就值錢了嗎?就像現實世界中無法復制的名畫一樣啊!
怎么估值?就進行所謂的 ICO(Initial Coin Offering)啊,就是我和李哭來老師成立一個基金,舉行一張發布會,就說我們給這 2100 張照片估值個 1.05 億!每張照片 5 萬!
我們先丟 5050 萬進去認購前 1100 張,其他猥瑣男可以眾籌 5000 萬買剩下的 1000 張照片,不想要了?賣給我和李哭來基金就行,我們認它值錢啊,我們這么牛逼不會騙你的。
自從有了區塊鏈——我們再也不怕 1025 們作惡,給我們彈窗小廣告,給我們下病毒了;再也不怕陳老師的照片丟失被破壞了,1984 老大哥復活都做不到.....
當然,也有煩惱,就是有炒名畫的現在來炒“陳老師plus”照片了,把價格搞得很高。
還有的更猥瑣,為了賺錢,自己拍了一堆裸照按照這個模式弄了個“某某老師plus”東施效顰收割韭菜,這就叫山寨幣,現在大概快一百種了吧。
看到這里,你肯定能明白區塊鏈了......最后,教大家怎么用 200 行 Go 代碼寫一個自己的區塊鏈!
只用 200 行 Go 代碼寫一個自己的區塊鏈
這篇文章就是幫助你使用 Go 語言來實現一個簡單的區塊鏈,用不到 200 行代碼來揭示區塊鏈的原理!
“用不到 200 行 Go 代碼就能實現一個自己的區塊鏈!” 聽起來有意思嗎?有什么能比開發一個自己的區塊鏈更好的學習實踐方法呢?那我們就一起來實踐下!
因為我們是一家從事醫療健康領域的科技公司,所以我們采用人類平靜時的心跳數據(BPM 心率)作為這篇文章中的示例數據。
讓我們先來統計一下你一分鐘內的心跳數,然后記下來,這個數字可能會在接下來的內容中用到。
通過本文,你將可以做到:
- 創建自己的區塊鏈
- 理解 hash 函數是如何保持區塊鏈的完整性
- 如何創造并添加新的塊
- 多個節點如何競爭生成塊
- 通過瀏覽器來查看整個鏈
- 所有其他關于區塊鏈的基礎知識
但是,對于比如工作量證明算法(PoW)以及權益證明算法(PoS)這類的共識算法文章中將不會涉及。
同時為了讓你更清楚得查看區塊鏈以及塊的添加,我們將網絡交互的過程簡化了,關于 P2P 網絡比如“全網廣播”這個過程等內容將在下一篇文章中補上。讓我們開始吧!
設置
我們假設你已經具備一點 Go 語言的開發經驗。在安裝和配置 Go 開發環境后之后,我們還要獲取以下一些依賴:
- go get github.com/davecgh/go-spew/spew
spew 可以幫助我們在 console 中直接查看 struct 和 slice 這兩種數據結構。
- go get github.com/gorilla/mux
Gorilla 的 mux 包非常流行, 我們用它來寫 Web handler。
- go get github.com/joho/godotenv
godotenv 可以幫助我們讀取項目根目錄中的 .env 配置文件,這樣我們就不用將 http port 之類的配置硬編碼進代碼中了。比如像這樣:
- ADDR=8080
接下來,我們創建一個 main.go 文件。之后我們的大部分工作都圍繞這個文件,讓我開始編碼吧!
導入依賴
我們將所有的依賴包以聲明的方式導入進去:
- package main
- import (
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "io"
- "log"
- "net/http"
- "os"
- "time"
- "github.com/davecgh/go-spew/spew"
- "github.com/gorilla/mux"
- "github.com/joho/godotenv"
- )
數據模型
接著我們來定義一個結構體,它代表組成區塊鏈的每一個塊的數據模型:
- type Block struct {
- Index int
- Timestamp string
- BPM int
- Hash string
- PrevHash string
- }
- Index 是這個塊在整個鏈中的位置。
- Timestamp 顯而易見就是塊生成時的時間戳。
- Hash 是這個塊通過 SHA256 算法生成的散列值。
- PrevHash 代表前一個塊的 SHA256 散列值。
- BPM 每分鐘心跳數,也就是心率。還記得文章開頭說到的嗎?
接著,我們再定義一個結構表示整個鏈,最簡單的表示形式就是一個 Block 的 slice:
- var Blockchain []Block
我們使用散列算法(SHA256)來確定和維護鏈中塊和塊正確的順序,確保每一個塊的 PrevHash 值等于前一個塊中的 Hash 值,這樣就以正確的塊順序構建出鏈:
散列和生成塊
我們為什么需要散列?主要是兩個原因:
- 在節省空間的前提下去唯一標識數據。散列是用整個塊的數據計算得出,在我們的例子中,將整個塊的數據通過 SHA256 計算成一個定長不可偽造的字符串。
- 維持鏈的完整性。通過存儲前一個塊的散列值,我們就能夠確保每個塊在鏈中的正確順序。任何對數據的篡改都將改變散列值,同時也就破壞了鏈。
以我們從事的醫療健康領域為例,比如有一個惡意的第三方為了調整“人壽險”的價格,而修改了一個或若干個塊中的代表不健康的 BPM 值,那么整個鏈都變得不可信了。
我們接著寫一個函數,用來計算給定的數據的 SHA256 散列值:
- func calculateHash(block Block) string {
- record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
- h := sha256.New()
- h.Write([]byte(record))
- hashed := h.Sum(nil)
- return hex.EncodeToString(hashed)
- }
這個 calculateHash 函數接受一個塊,通過塊中的 Index,Timestamp,BPM,以及 PrevHash 值來計算出 SHA256 散列值。
接下來我們就能便攜一個生成塊的函數:
- func generateBlock(oldBlock Block, BPM int) (Block, error) {
- var newBlock Block
- t := time.Now()
- newBlock.Index = oldBlock.Index + 1
- newBlock.Timestamp = t.String()
- newBlock.BPM = BPM
- newBlock.PrevHash = oldBlock.Hash
- newBlock.Hash = calculateHash(newBlock)
- return newBlock, nil
- }
其中,Index 是從給定的前一塊的 Index 遞增得出,時間戳是直接通過 time.Now() 函數來獲得的,Hash 值通過前面的 calculateHash 函數計算得出,PrevHash 則是給定的前一個塊的 Hash 值。
校驗塊
搞定了塊的生成,接下來我們需要有函數幫我們判斷一個塊是否有被篡改。檢查 Index 來看這個塊是否正確得遞增,檢查 PrevHash 與前一個塊的 Hash 是否一致,再來通過 calculateHash 檢查當前塊的 Hash 值是否正確。
通過這幾步我們就能寫出一個校驗函數:
- func isBlockValid(newBlock, oldBlock Block) bool {
- if oldBlock.Index+1 != newBlock.Index {
- return false
- }
- if oldBlock.Hash != newBlock.PrevHash {
- return false
- }
- if calculateHash(newBlock) != newBlock.Hash {
- return false
- }
- return true
- }
除了校驗塊以外,我們還會遇到一個問題:兩個節點都生成塊并添加到各自的鏈上,那我們應該以誰為準?這里的細節我們留到下一篇文章,這里先讓我們記住一個原則:始終選擇最長的鏈。
通常來說,更長的鏈表示它的數據(狀態)是更新的,所以我們需要一個函數能幫我們將本地的過期的鏈切換成最新的鏈:
- func replaceChain(newBlocks []Block) {
- if len(newBlocks) > len(Blockchain) {
- Blockchain = newBlocks
- }
- }
到這一步,我們基本就把所有重要的函數完成了。接下來,我們需要一個方便直觀的方式來查看我們的鏈,包括數據及狀態。通過瀏覽器查看 Web 頁面可能是最合適的方式!
Web 服務
我猜你一定對傳統的 Web 服務及開發非常熟悉,所以這部分你肯定一看就會。
借助 Gorilla/mux 包,我們先寫一個函數來初始化我們的 Web 服務:
- func run() error {
- mux := makeMuxRouter()
- httpAddr := os.Getenv("ADDR")
- log.Println("Listening on ", os.Getenv("ADDR"))
- s := &http.Server{
- Addr: ":" + httpAddr,
- Handler: mux,
- ReadTimeout: 10 * time.Second,
- WriteTimeout: 10 * time.Second,
- MaxHeaderBytes: 1 << 20,
- }
- if err := s.ListenAndServe(); err != nil {
- return err
- }
- return nil
- }
其中的端口號是通過前面提到的 .env 來獲得,再添加一些基本的配置參數,這個 web 服務就已經可以 listen and serve 了!
接下來我們再來定義不同 endpoint 以及對應的 handler。例如,對“/”的 GET 請求我們可以查看整個鏈,“/”的 POST 請求可以創建塊。
- func makeMuxRouter() http.Handler {
- muxRouter := mux.NewRouter()
- muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
- muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
- return muxRouter
- }
GET 請求的 handler:
- func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
- bytes, err := json.MarshalIndent(Blockchain, "", " ")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- io.WriteString(w, string(bytes))
- }
為了簡化,我們直接以 JSON 格式返回整個鏈,你可以在瀏覽器中訪問 localhost:8080 或者 127.0.0.1:8080 來查看(這里的 8080 就是你在 .env 中定義的端口號 ADDR)。
POST 請求的 handler 稍微有些復雜,我們先來定義一下 POST 請求的 payload:
- type Message struct {
- BPM int
- }
再看看 handler 的實現:
- func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
- var m Message
- decoder := json.NewDecoder(r.Body)
- if err := decoder.Decode(&m); err != nil {
- respondWithJSON(w, r, http.StatusBadRequest, r.Body)
- return
- }
- defer r.Body.Close()
- newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
- if err != nil {
- respondWithJSON(w, r, http.StatusInternalServerError, m)
- return
- }
- if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
- newBlockchain := append(Blockchain, newBlock)
- replaceChain(newBlockchain)
- spew.Dump(Blockchain)
- }
- respondWithJSON(w, r, http.StatusCreated, newBlock)
- }
我們的 POST 請求體中可以使用上面定義的 payload,比如:
- {"BPM":75}
還記得前面我們寫的 generateBlock 這個函數嗎?它接受一個“前一個塊”參數,和一個 BPM 值。
POST handler 接受請求后就能獲得請求體中的 BPM 值,接著借助生成塊的函數以及校驗塊的函數就能生成一個新的塊了!
除此之外,你也可以:
- 使用 spew.Dump 這個函數可以以非常美觀和方便閱讀的方式將 struct、slice 等數據打印在控制臺里,方便我們調試。
- 測試 POST 請求時,可以使用 POSTMAN 這個 chrome 插件,相比 curl它更直觀和方便。
POST 請求處理完之后,無論創建塊成功與否,我們需要返回客戶端一個響應:
- func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
- response, err := json.MarshalIndent(payload, "", " ")
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- w.Write([]byte("HTTP 500: Internal Server Error"))
- return
- }
- w.WriteHeader(code)
- w.Write(response)
- }
快要大功告成了
接下來,我們把這些關于區塊鏈的函數,Web 服務的函數“組裝”起來:
- func main() {
- err := godotenv.Load()
- if err != nil {
- log.Fatal(err)
- }
- go func() {
- t := time.Now()
- genesisBlock := Block{0, t.String(), 0, "", ""}
- spew.Dump(genesisBlock)
- Blockchain = append(Blockchain, genesisBlock)
- }()
- log.Fatal(run())
- }
這里的 genesisBlock (創世塊)是 main 函數中最重要的部分,通過它來初始化區塊鏈,畢竟第一個塊的 PrevHash 是空的。
哦耶!完成了
你們可以從這里獲得完整的代碼:Github repo[1]
讓我們來啟動它:
- go run main.go
在終端中,我們可以看到 web 服務器啟動的日志信息,并且打印出了創世塊的信息:
接著我們打開瀏覽器,訪問 localhost:8080 這個地址,我們可以看到頁面中展示了當前整個區塊鏈的信息(當然,目前只有一個創世塊):
接著,我們再通過 POSTMAN 來發送一些 POST 請求:
刷新剛才的頁面,現在的鏈中多了一些塊,正是我們剛才生成的,同時你們可以看到,塊的順序和散列值都正確。
下一步
剛剛我們完成了一個自己的區塊鏈,雖然很簡單(陋),但它具備塊生成、散列計算、塊校驗等基本能力。
接下來你就可以繼續深入的學習區塊鏈的其他重要知識,比如工作量證明、權益證明這樣的共識算法,或者是智能合約、Dapp、側鏈等等。
目前這個實現中不包括任何 P2P 網絡的內容,我們會在下一篇文章中補充這部分內容,當然,我們鼓勵你在這個基礎上自己實踐一遍!