區塊鏈的原理與golang實現例子
什麼是區塊鏈
區塊鏈有多火,就不用我介紹了,你能通過搜尋引擎跳轉到這裡,就證明你是區塊鏈的fan了。既然進來了,就不會讓你白來,no bb, 直接上乾貨!
(開場白,也不全是廢話)區塊鏈是 21 世紀最具革命性的技術之一,它仍然處於不斷成長的階段,而且還有很多潛力尚未顯現出來。 本質上,區塊鏈只是一個分散式資料庫而已。 不過,使它獨一無二的是,區塊鏈是一個公開的資料庫,而不是一個私人資料庫,也就是說,每個使用它的人都有一個完整或部分的副本。 只有經過其他資料庫管理員的同意,才能向資料庫中新增新的記錄。 此外,也正是由於區塊鏈,才使得加密貨幣和智慧合約成為現實。
綜而述之,用一個形象的比如:區塊鏈就是一個去中心化、分散式”記賬本”。
區塊鏈原理
1.區塊
讓我們從 “區塊鏈” 中的 “區塊” 談起。在區塊鏈中,儲存有效資訊的是區塊。
比如,比特幣區塊儲存的有效資訊,就是比特幣交易,交易資訊也是所有加密貨幣的本質。除此以外,區塊還包含了一些技術資訊,比如版本,當前時間戳和前一個區塊的雜湊。
這裡,我們並不會實現一個像比特幣技術規範所描述的區塊鏈,而是實現一個簡化版的區塊鏈,它僅包含了一些關鍵資訊。看起來就像是這樣:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
}
- Timestamp 是當前時間戳,也就是區塊建立的時間。
- Data 是區塊儲存的實際有效的資訊。
- PrevBlockHash 儲存的是前一個塊的雜湊。
- Hash 是當前塊的雜湊。
在比特幣技術規範中,Timestamp, PrevBlockHash, Hash 是區塊頭(block header),區塊頭是一個單獨的資料結構。而交易,也就是這裡的 Data, 是另一個單獨的資料結構。為了簡便起見,我把這兩個混合在了一起。
那麼,我們要如何計算雜湊呢?如何計算雜湊,是區塊鏈一個非常重要的部分。正是由於這個特性,才使得區塊鏈是安全的。計算一個雜湊,是在計算上非常困難的一個操作。即使在高速電腦上,也要花費不少時間 (這就是為什麼人們會購買 GPU 來挖比特幣) 。這是一個有意為之的架構設計,它故意使得加入新的區塊十分困難,因此可以保證區塊一旦被加入以後,就很難再進行修改。
目前,我們僅取了 Block 結構的一些欄位(Timestamp, Data 和 PrevBlockHash),並將它們相互連線起來,然後在連線後的結果上計算一個 SHA-256 的雜湊. 讓我們在 SetHash 方法中完成這個任務:
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
接下來,按照 Golang 的慣例,我們會實現一個用於簡化建立一個區塊的函式:
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.SetHash()
return block
}
這就是區塊部分的全部內容了!
2.鏈
下面讓我們來實現一個區塊鏈。本質上,區塊鏈僅僅是一個有著特定結構的資料庫,是一個有序,後向連線的列表。
這也就是說,區塊按照插入的順序進行儲存,每個塊都被連線到前一個塊。這樣的結構,能夠讓我們快速地獲取鏈上的最新塊,並且高效地通過雜湊來檢索一個塊。
在 Golang 中,可以通過一個 array 和 map 來實現這個結構:array 儲存有序的雜湊(Golang 中 array 是有序的),map 儲存 hask -> block 對(Golang 中, map 是無序的)。 但是在基本的原型階段,我們只用到了 array,因為現在還不需要通過雜湊來獲取塊。
type Blockchain struct {
blocks []*Block
}
這就是我們的第一個區塊鏈!我從來沒有想過它會是這麼容易。
現在,讓我們能夠給它新增一個塊:
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
}
完成!不過,真的就這樣了嗎?
為了加入一個新的塊,我們必須要有一個已有的塊,但是,現在我們的鏈是空的,一個塊都沒有!所以,在任何一個區塊鏈中,都必須至少有一個塊。這樣的塊,也就是鏈中的第一個塊,通常叫做創世塊(genesis block). 讓我們實現一個方法來建立一個創世塊:
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
現在,我們可以實現一個函式來建立有創世塊的區塊鏈:
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
來檢查一個我們的區塊鏈是否如期工作:
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
for _, block := range bc.blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
}
一個完整的程式碼
package main
import (
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"sort"
"strings"
"time"
"websocket"
//"golang.org/x/net/websocket"
)
const (
queryLatest = iota
queryAll
responseBlockchain
)
var genesisBlock = &Block{
Index: 0,
PreviousHash: "0",
Timestamp: 1465154705,
Data: "my genesis block!!",
Hash: "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
}
var (
sockets []*websocket.Conn
blockchain = []*Block{genesisBlock}
httpAddr = flag.String("api", ":3001", "api server address.")
p2pAddr = flag.String("p2p", ":6001", "p2p server address.")
initialPeers = flag.String("peers", "ws://localhost:6001", "initial peers")
)
type Block struct {
Index int64 `json:"index"`
PreviousHash string `json:"previousHash"`
Timestamp int64 `json:"timestamp"`
Data string `json:"data"`
Hash string `json:"hash"`
}
func (b *Block) String() string {
return fmt.Sprintf("index: %d,previousHash:%s,timestamp:%d,data:%s,hash:%s", b.Index, b.PreviousHash, b.Timestamp, b.Data, b.Hash)
}
type ByIndex []*Block
func (b ByIndex) Len() int { return len(b) }
func (b ByIndex) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b ByIndex) Less(i, j int) bool { return b[i].Index < b[j].Index }
type ResponseBlockchain struct {
Type int `json:"type"`
Data string `json:"data"`
}
func errFatal(msg string, err error) {
if err != nil {
log.Fatalln(msg, err)
}
}
func connectToPeers(peersAddr []string) {
for _, peer := range peersAddr {
if peer == "" {
continue
}
ws, err := websocket.Dial(peer, "", peer)
if err != nil {
log.Println("dial to peer", err)
continue
}
initConnection(ws)
}
}
func initConnection(ws *websocket.Conn) {
go wsHandleP2P(ws)
log.Println("query latest block.")
ws.Write(queryLatestMsg())
}
func handleBlocks(w http.ResponseWriter, r *http.Request) {
bs, _ := json.Marshal(blockchain)
w.Write(bs)
}
func handleMineBlock(w http.ResponseWriter, r *http.Request) {
var v struct {
Data string `json:"data"`
}
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
err := decoder.Decode(&v)
if err != nil {
w.WriteHeader(http.StatusGone)
log.Println("[API] invalid block data : ", err.Error())
w.Write([]byte("invalid block data. " + err.Error() + "\n"))
return
}
block := generateNextBlock(v.Data)
addBlock(block)
broadcast(responseLatestMsg())
}
func handlePeers(w http.ResponseWriter, r *http.Request) {
var slice []string
for _, socket := range sockets {
if socket.IsClientConn() {
slice = append(slice, strings.Replace(socket.LocalAddr().String(), "ws://", "", 1))
} else {
slice = append(slice, socket.Request().RemoteAddr)
}
}
bs, _ := json.Marshal(slice)
w.Write(bs)
}
func handleAddPeer(w http.ResponseWriter, r *http.Request) {
var v struct {
Peer string `json:"peer"`
}
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
err := decoder.Decode(&v)
if err != nil {
w.WriteHeader(http.StatusGone)
log.Println("[API] invalid peer data : ", err.Error())
w.Write([]byte("invalid peer data. " + err.Error()))
return
}
connectToPeers([]string{v.Peer})
}
func wsHandleP2P(ws *websocket.Conn) {
var (
v = &ResponseBlockchain{}
peer = ws.LocalAddr().String()
)
sockets = append(sockets, ws)
for {
var msg []byte
err := websocket.Message.Receive(ws, &msg)
if err == io.EOF {
log.Printf("p2p Peer[%s] shutdown, remove it form peers pool.\n", peer)
break
}
if err != nil {
log.Println("Can't receive p2p msg from ", peer, err.Error())
break
}
log.Printf("Received[from %s]: %s.\n", peer, msg)
err = json.Unmarshal(msg, v)
errFatal("invalid p2p msg", err)
switch v.Type {
case queryLatest:
v.Type = responseBlockchain
bs := responseLatestMsg()
log.Printf("responseLatestMsg: %s\n", bs)
ws.Write(bs)
case queryAll:
d, _ := json.Marshal(blockchain)
v.Type = responseBlockchain
v.Data = string(d)
bs, _ := json.Marshal(v)
log.Printf("responseChainMsg: %s\n", bs)
ws.Write(bs)
case responseBlockchain:
handleBlockchainResponse([]byte(v.Data))
}
}
}
func getLatestBlock() (block *Block) { return blockchain[len(blockchain)-1] }
func responseLatestMsg() (bs []byte) {
var v = &ResponseBlockchain{Type: responseBlockchain}
d, _ := json.Marshal(blockchain[len(blockchain)-1:])
v.Data = string(d)
bs, _ = json.Marshal(v)
return
}
func queryLatestMsg() []byte { return []byte(fmt.Sprintf("{\"type\": %d}", queryLatest)) }
func queryAllMsg() []byte { return []byte(fmt.Sprintf("{\"type\": %d}", queryAll)) }
func calculateHashForBlock(b *Block) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%s%d%s", b.Index, b.PreviousHash, b.Timestamp, b.Data))))
}
func generateNextBlock(data string) (nb *Block) {
var previousBlock = getLatestBlock()
nb = &Block{
Data: data,
PreviousHash: previousBlock.Hash,
Index: previousBlock.Index + 1,
Timestamp: time.Now().Unix(),
}
nb.Hash = calculateHashForBlock(nb)
return
}
func addBlock(b *Block) {
if isValidNewBlock(b, getLatestBlock()) {
blockchain = append(blockchain, b)
}
}
func isValidNewBlock(nb, pb *Block) (ok bool) {
if nb.Hash == calculateHashForBlock(nb) &&
pb.Index+1 == nb.Index &&
pb.Hash == nb.PreviousHash {
ok = true
}
return
}
func isValidChain(bc []*Block) bool {
if bc[0].String() != genesisBlock.String() {
log.Println("No same GenesisBlock.", bc[0].String())
return false
}
var temp = []*Block{bc[0]}
for i := 1; i < len(bc); i++ {
if isValidNewBlock(bc[i], temp[i-1]) {
temp = append(temp, bc[i])
} else {
return false
}
}
return true
}
func replaceChain(bc []*Block) {
if isValidChain(bc) && len(bc) > len(blockchain) {
log.Println("Received blockchain is valid. Replacing current blockchain with received blockchain.")
blockchain = bc
broadcast(responseLatestMsg())
} else {
log.Println("Received blockchain invalid.")
}
}
func broadcast(msg []byte) {
for n, socket := range sockets {
_, err := socket.Write(msg)
if err != nil {
log.Printf("peer [%s] disconnected.", socket.RemoteAddr().String())
sockets = append(sockets[0:n], sockets[n+1:]...)
}
}
}
func handleBlockchainResponse(msg []byte) {
var receivedBlocks = []*Block{}
err := json.Unmarshal(msg, &receivedBlocks)
errFatal("invalid blockchain", err)
sort.Sort(ByIndex(receivedBlocks))
latestBlockReceived := receivedBlocks[len(receivedBlocks)-1]
latestBlockHeld := getLatestBlock()
if latestBlockReceived.Index > latestBlockHeld.Index {
log.Printf("blockchain possibly behind. We got: %d Peer got: %d", latestBlockHeld.Index, latestBlockReceived.Index)
if latestBlockHeld.Hash == latestBlockReceived.PreviousHash {
log.Println("We can append the received block to our chain.")
blockchain = append(blockchain, latestBlockReceived)
} else if len(receivedBlocks) == 1 {
log.Println("We have to query the chain from our peer.")
broadcast(queryAllMsg())
} else {
log.Println("Received blockchain is longer than current blockchain.")
replaceChain(receivedBlocks)
}
} else {
log.Println("received blockchain is not longer than current blockchain. Do nothing.")
}
}
func main() {
flag.Parse()
connectToPeers(strings.Split(*initialPeers, ","))
http.HandleFunc("/blocks", handleBlocks)
http.HandleFunc("/mine_block", handleMineBlock)
http.HandleFunc("/peers", handlePeers)
http.HandleFunc("/add_peer", handleAddPeer)
go func() {
log.Println("Listen HTTP on", *httpAddr)
errFatal("start api server", http.ListenAndServe(*httpAddr, nil))
}()
http.Handle("/", websocket.Handler(wsHandleP2P))
log.Println("Listen P2P on ", *p2pAddr)
errFatal("start p2p server", http.ListenAndServe(*p2pAddr, nil))
}
連結: https://github.com/kofj/naivechain/blob/master/main.go
多少人忙得連寫部落格的時間都沒有喲!
相關文章
- 區塊鏈-NFT 的實現原理區塊鏈
- 200行golang 實現的區塊鏈Golang區塊鏈
- 區塊鏈教程、區塊鏈指南、區塊鏈中文手冊、區塊鏈原理區塊鏈
- 區塊鏈原理區塊鏈
- 區塊鏈,中心去,何曾著眼看君王?用Go語言實現區塊鏈技術,透過Golang秒懂區塊鏈區塊鏈Golang
- JavaScript實現區塊鏈JavaScript區塊鏈
- 雲+區塊鏈 實現區塊鏈技術的普惠應用區塊鏈
- (二)區塊鏈的共識演算法:PoS 及其 例子 程式碼 實現區塊鏈演算法
- 【區塊鏈技術實現】區塊鏈
- 基於區塊鏈的智慧鎖設計與實現區塊鏈
- 一文讀懂區塊鏈以及一個區塊鏈的實現區塊鏈
- 區塊鏈記賬原理區塊鏈
- [上海] golang/區塊鏈開發招聘Golang區塊鏈
- 區塊鏈的原理是什麼?區塊鏈
- 區塊鏈錢包的技能原理區塊鏈
- 區塊鏈學習-Golang 與智慧合約的互動(一)區塊鏈Golang
- 比特幣和區塊鏈(2):比特幣中區塊鏈的實現比特幣區塊鏈
- .Net Core實現區塊鏈初探區塊鏈
- 使用Javascript實現小型區塊鏈JavaScript區塊鏈
- 在 iOS 中實現區塊鏈iOS區塊鏈
- 區塊鏈3.0,人工智慧與區塊鏈的完美融合區塊鏈人工智慧
- NodeJS實現簡易區塊鏈NodeJS區塊鏈
- 區塊鏈安全:基於區塊鏈網路攻擊的方式原理詳解區塊鏈
- V神:區塊鏈上投票流程的實現區塊鏈
- 區塊鏈特徵與區塊鏈技術應用落地區塊鏈特徵
- 區塊鏈開發公司區塊鏈與產業變革區塊鏈產業
- 量子計算與區塊鏈碰撞後——量子區塊鏈區塊鏈
- 區塊鏈:「重回現在」的枷鎖與契機區塊鏈
- 區塊鏈篇:區塊鏈的創新與顛覆(附下載)區塊鏈
- 招聘Golang區塊鏈開發工程師Golang區塊鏈工程師
- 基於區塊鏈的隱私計算 - 原理和實踐區塊鏈
- 區塊鏈系列6-區塊鏈安全與大資料區塊鏈大資料
- golang reflect 實現原理Golang
- 使用MVC模式實現區塊鏈開發MVC模式區塊鏈
- [譯] 用 Java 程式碼實現區塊鏈Java區塊鏈
- 區塊鏈與金融的結合區塊鏈
- 用java實現一個簡單的區塊鏈Java區塊鏈
- 一個簡單的區塊鏈程式碼實現區塊鏈