阅读 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 其他部分进行分析。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于