閱讀fabric源碼的共識機制部分,感覺源碼難度還是有的,所以先從最簡單的requeststore開始吧。
在閱讀了部分超級賬本的源碼后,有一個經驗就是,在閱讀源碼特別是大項目的源碼時,可能會感到無所適從,其實這也是很正常的,我的經驗是可以先從一條線開始理清代碼的執行流。比如像 hyperledger 這樣的平台,可以從鏈碼的執行這條線來看源碼,跟着調試一步步走,相信會簡單不少。
但是對於那些不是很好調試的代碼來說,還有一個簡單的方法,就是看代碼的單元測試的程序,體會它是怎么使用的,這其實也是一個比較好的方法,下面分析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開始看,它測試了兩個函數:
- TestOrderedRequests(t *testing.T)
- BenchmarkOrderedRequests(b *testing.B)
這里的第一個測試函數是普通的測試函數,第二個是benchmark測試函數(注意*testing.B)
先看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構造了一個請求就行了。
繼續看orderedRequests:
type orderedRequests struct {
order list.List
presence map[string]*list.Element
}
它保存着一個列表,還有一個map,這里的map鍵是list元素的hash,值對應於list的元素。
繼續看: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其他部分進行分析。
