如今不少人都在從事區塊鏈方面的研究,做者也一直在基於Hyperledger Fabric作一些開發工做。爲了方便後來人更快的入門,本着「開源」的精神,在本文中向你們講解一下Hyperledger Fabric帳本的結構和原理。做者解析的Fabric的工程版本爲v1.0.1,在新版本中可能會有些許誤差。docker
ps:做者默認各位讀者已經具有了必定的區塊鏈基本知識,再也不作一些基礎知識的闡述。數據庫
在做者最初瞭解bitcoin的時候有一個疑問:礦工如何校驗一筆交易中引入的Utxo是否合法呢?若是過是從創世塊開始遍歷工做量會隨着塊的增長而逐漸變大。看成者研究過Bitcoin的源碼後發現,在Bitcoin中有一部分模塊是用來存儲當前全部Utxo的,它採用的是levelDB數據庫,在校驗一筆交易中的Utxo是否合法時直接去DB中檢索便可。服務器
Bitcoin的這種作法給做者總結爲以下:「單純」的區塊鏈帳本存儲區塊數據便可,而爲了方便支持各類功能每每會提取出一些數據(重要的、頻繁訪問的)獨立存儲。在Bitcoin的世界中礦工的共識其實是對當前Utxo的共識,因此它將Utxo從區塊量帳本中提取出來獨立的存儲,那在Hyperledger Fabric中須要從區塊帳本中提取出的數據是什麼?分佈式
Fabric中是一個針對商業應用的分佈式帳本技術,自己並不存在代幣,你能夠在它的基礎上進行二次開發,利用Fabric的智能合約實現本身須要的各類業務--包括髮放代幣。在它的智能合約模塊中有兩個最關鍵的接口中:shim.PutState(Key, Value) 和 shim.GetState(Key),這個是用來向節點本地讀寫數據的指令,它呈現給智能合約的是一個key-value結構的非關係形數據庫。而後Client經過調用智能合約執行業務邏輯,智能合約來進行數據的讀寫操做的(在區塊鏈的世界中調用智能合約的Request統稱爲Transaction,這是約定俗稱的,雖然在Fabric中沒有代幣),這和傳統的中心化Web服務十分類似,只不過Tomcat換成了Fabric,然後臺的jar包換成了智能合約,中心化的APP變成了分佈式的DAPP。ide
如今參照做者從Bitcoin中總結的規律,在Fabric智能合約中讀寫的業務數據符合重要的、頻繁訪問的特徵,應該獨立存儲,這個數據庫的名稱爲StateDB 。除了StateDB之外咱們還要保存區塊數據,在Fabric裏面它有一個本身的FileSystem,用來存儲區塊數據,這個文件系統是存儲在本地的文件中的。區塊數據被連續的寫入到本地的BlockFile中,經過File.io來讀寫數據,Fabric工程是用go語言編寫的,工程中操做文件IO的包是os ,包中經常使用操做文件的函數以下: 函數
type File
func Create(name string) (*File, error)
func NewFile(fd uintptr, name string) *File
func Open(name string) (*File, error)
func OpenFile(name string, flag int, perm FileMode) (*File, error)
func Pipe() (r *File, w *File, err error)
func (f *File) Chdir() error
func (f *File) Chmod(mode FileMode) error
func (f *File) Chown(uid, gid int) error
func (f *File) Close() error
func (f *File) Fd() uintptr
func (f *File) Name() string
func (f *File) Read(b []byte) (n int, err error)
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
func (f *File) Readdir(n int) ([]FileInfo, error)
func (f *File) Readdirnames(n int) (names []string, err error)
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
func (f *File) SetDeadline(t time.Time) error
func (f *File) SetReadDeadline(t time.Time) error
func (f *File) SetWriteDeadline(t time.Time) error
func (f *File) Stat() (FileInfo, error)
func (f *File) Sync() error
func (f *File) Truncate(size int64) error
func (f *File) Write(b []byte) (n int, err error)
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
func (f *File) WriteString(s string) (n int, err error)
type FileInfo
func Lstat(name string) (FileInfo, error)
func Stat(name string) (FileInfo, error)
type FileMode
func (m FileMode) IsDir() bool
func (m FileMode) IsRegular() bool
func (m FileMode) Perm() FileMode
func (m FileMode) String() string
Q1:以上全部的接口都不支持複雜的查詢功能,讀者們能夠想象一下,當要去BlockFile中讀取一個塊的時候要什麼樣的條件才能快速的找到對應塊?源碼分析
A1: 若是查詢者能知道一個Block數據在文件中的便宜量則能夠快速定位到Block,相反則只能經過遍歷的方式。爲了解決這個問題Fabric 將Block在BlockFile文件中的偏移量記載到了一個非關係形DB中,這個DB的名稱爲indexDB。區塊鏈
Q2:區塊數據是不可篡改的,也就是說BlockFile的size是持續增加的,若是size過大超過了位置偏移量的最大範圍怎麼辦?ui
A2: 將區塊數據存儲在多個文件塊中,以blockfile_ 爲前綴,以塊的生成次序爲後綴。spa
解決了上面的兩個問題後,查詢一個塊數據的過程以下:
setup1: 從indexDB中讀取Block的位置信息(blockfile的編號、位置偏移量);
setup2: 打開對應的blockfile,位移到指定位置,讀取Block數據。
Fabric帳本的總體結構圖來以下圖所示:
其中ledgersData是整個帳本的根目錄,做者會逐個文件夾進行解析:
(1)chains:chains/chains下包含的mychannel是對應的channel的名稱,由於Fabric是有多channel的機制,而channel之間的帳本是隔離的,每一個channel都有本身的帳本空間。chains/index下面包含的是levelDB數據庫文件,在Fabric中默認全部的數據庫都是LevelDB,這個緣由做者下面會講到,DB中存儲的就是咱們上面說的區塊索引部分。chains/chains和chains/index就是上面所說的File System和indexDB;
(2)stateLeveldb: 一樣是levelDB數據庫,存儲的就是咱們上面所說的智能合約putstate寫入的數據;
(3)ledgerProvider:數據庫內存儲的是當前節點所包含channel的信息(已經建立的channel id 和正在建立中的channel id),主要是爲了Fabric的多channel機制服務的;
(4)historyLeveldb:數據庫內存儲的是智能合約中寫入的key的歷史記錄的索引地址。
這一部分做者會結合幾個問題對Hyperledger Fabric 區塊鏈帳本的內在原理進行深刻的剖析,其中會涉及到源碼部分的解析,若是讀者只是想對帳本機制作一些簡單瞭解,能夠跳過源碼分析的部分。
Q3: 智能合約讀寫數據到stateLeveldb的流程?
A3:做者以GetState()接口爲例進行說明:
首先,讀者們須要明確Hyperledger Fabric的智能合約是在docker容器中運行的,而stateLeveldb是位於節點服務器本地的。同時,Fabric沒有將stateLevelDB映射到docker容器中,因此智能合約不能直接訪問stateLevelDB。
Fabric中是經過rpc服務調用的方式,來解決這個問題的。開發過Fabric智能合約的讀者應該清楚,Fabric提供了一箇中間層Shim包,同時在Peer節點中默認會開啓一個grpc服務 ChainCodeService。智能合約文件在編譯的時候會自動引入shim包,而shim包中在GetState的過程當中會向節點的ChainCodeService發送請求到節點,而後節點再從本地的stateLevelDB中讀取請求的數據,返回給智能合約。
流程圖:
以上只是讀數據的過程,寫數據遠比讀數據更爲複雜。
//TODO 寫數據過程
Q4:fabric支持交易的查詢接口,那麼交易查詢是如何實現的?
A4: 交易查詢與Block查詢的實現原理一致,節點在寫入帳本到本地的時候,會將交易在FileSystem中的索引也寫入indexDB中。
Q5: indexDB是levelDB,它是如何實現複雜查詢的,好比交易的範圍查詢?
未完待續... ...