什么是区块链
区块链有多火,就不用我介绍了,你能通过搜索引擎跳转到这里,就证明你是区块链的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
多少人忙得连写博客的时间都没有哟!