閱讀fabric
源碼的共識機制部分,感受源碼難度仍是有的,因此先從最簡單的requeststore
開始吧。golang
在閱讀了部分超級帳本的源碼後,有一個經驗就是,在閱讀源碼特別是大項目的源碼時,可能會感到無所適從,其實這也是很正常的,個人經驗是能夠先從一條線開始理清代碼的執行流。好比像 hyperledger 這樣的平臺,能夠從鏈碼的執行
這條線來看源碼,跟着調試一步步走,相信會簡單很多。app
可是對於那些不是很好調試的代碼來講,還有一個簡單的方法,就是看代碼的單元測試的程序,體會它是怎麼使用的,這其實也是一個比較好的方法,下面分析pbft
的實現源碼,就是使用這種方法來分析的。函數
pbft
實現起來不容易,這裏從它最簡單的部分入手,話很少說,看代碼吧:單元測試
// consensus/pbft/requeststore_test.go func TestOrderedRequests(t *testing.T) { or := &orderedRequests{} or.empty() r1 := createPbftReq(2, 1) r2 := createPbftReq(2, 2) r3 := createPbftReq(19, 1) if or.has(or.wrapRequest(r1).key) { t.Errorf("should not have req") } or.add(r1) if !or.has(or.wrapRequest(r1).key) { t.Errorf("should have req") } if or.has(or.wrapRequest(r2).key) { t.Errorf("should not have req") } if or.remove(r2) { t.Errorf("should not have removed req") } if !or.remove(r1) { t.Errorf("should have removed req") } if or.remove(r1) { t.Errorf("should not have removed req") } if or.order.Len() != 0 || len(or.presence) != 0 { t.Errorf("should have 0 len") } or.adds([]*Request{r1, r2, r3}) if or.order.Back().Value.(requestContainer).req != r3 { t.Errorf("incorrect order") } } func BenchmarkOrderedRequests(b *testing.B) { or := &orderedRequests{} or.empty() Nreq := 100000 reqs := make(map[string]*Request) for i := 0; i < Nreq; i++ { rc := or.wrapRequest(createPbftReq(int64(i), 0)) reqs[rc.key] = rc.req } b.ResetTimer() b.N = 100; fmt.Printf("N is %d\n", b.N) for i := 0; i < b.N; i++ { for _, r := range reqs { or.add(r) } for k := range reqs { _ = or.has(k) } for _, r := range reqs { or.remove(r) } } }
從requeststore_test.go
開始看,它測試了兩個函數:測試
這裏的第一個測試函數是普通的測試函數,第二個是benchmark測試函數(注意*testing.B)ui
先看createPbftReq這個函數:調試
// consensus/pbft/mock_utilities_test.go func createPbftReq(tag int64, replica uint64) (req *Request) { tx := createTx(tag) txPacked := marshalTx(tx) req = &Request{ Timestamp: tx.GetTimestamp(), ReplicaId: replica, Payload: txPacked, } return }
這裏就是使用傳過來的tag與replica構造了Request對象,其中tx的時間屬性(Seconds)與tag有關,在createTx還給定了tx的type,這些不是很重要,咱們只要知道是經過tag和replica構造了一個請求就好了。code
繼續看orderedRequests:對象
type orderedRequests struct { order list.List presence map[string]*list.Element }
它保存着一個列表,還有一個map,這裏的map鍵是list元素的hash,值對應於list的元素。rem
繼續看:wrapRequest函數
func (a *orderedRequests) wrapRequest(req *Request) requestContainer { return requestContainer{ key: hash(req), req: req, } }
就是把req變成 (hash(req), req)對,是否是很簡單。。
後面的測試邏輯就很簡單了,因此總的邏輯是:
建立空的orderedRequests並初始化
建立3個req
判斷or有沒有req1(此時爲空,固然沒有)
添加req1
判斷or有沒有req1(剛添加上,固然有)
判斷or有沒有req2(固然沒有)
刪掉r2(因此這個時候就能夠獲得源碼裏remove的做用,刪除成功返回true,不然返回false)
刪除r1(刪除成功)
再刪除r1(已爲空,刪除不成功)
檢查or是否爲空(爲空)
添加r1,r2,r3到or
看最後一個是否是r3(這也體現出requeststore是順序表)
第一個測試邏輯很是簡單,可是可讓咱們快速對源代碼文件有了必定了解。
下一個測試函數是一個benchmark函數,自己很是的簡單,我接觸golang不久,要提的主要是b.N
是函數執行的次數,golang會使用不一樣的N來調用函數,屢次測試取平均嘛,這個挺方便的。
另外測試結束後會提示:
100 1031034650 ns/op
它表示函數執行了100次,平次一次執行時間是1031034650納秒。
測試到此就結束了,再看源碼文件,測試沒覆蓋到的主要是:
type requestStore struct { outstandingRequests *orderedRequests pendingRequests *orderedRequests }
其餘的函數基本上都是很是簡單的,除了:
// getNextNonPending returns up to the next n outstanding, but not pending requests func (rs *requestStore) getNextNonPending(n int) (result []*Request) { for oreqc := rs.outstandingRequests.order.Front(); oreqc != nil; oreqc = oreqc.Next() { oreq := oreqc.Value.(requestContainer) if rs.pendingRequests.has(oreq.key) { continue } result = append(result, oreq.req) if len(result) == n { break } } return result }
這個函數主要是從outstandingRequests拿出不在pendingRequests的n個request。
上面就是這部分的內容,很是簡單的代碼,關鍵是看代碼的一個思路,後面會對pbft其餘部分進行分析。