阅读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其余部分进行分析。