Hyperledger Fabric原始碼分析之Gossip

語言: CN / TW / HK

Gossip演算法正如它的名字,小道訊息只需要有人傳播一次,那麼過一段時間,所有人都會知道,就像新冠病毒一樣,所以Gossip演算法也有其它的別名“傳染病擴散演算法”、“謠言傳播演算法”等等。在Fabric中Gossip演算法用於區塊的傳播,即共識後將得到的區塊廣播給組織內和其它組織。本節會介紹Gossip演算法的設計原理、資料結構和部分核心原始碼的實現,例如Push演算法、Pull演算法等。

1. 設計原理

Gossip協議可以抽象成廣播,即傳送節點向其它節點傳播同樣的訊息。在節點數量較少的情況下,分散式系統無需實現Gossip協議,僅僅依靠傳送節點多次傳送訊息即可。而當節點數量增加時,簡單的廣播會導致傳送節點資源消耗的線性增長,直至佔滿頻寬/CPU,從而產生較高的傳播延遲。Gossip協議本質上就是解決這樣的一個問題而被設計存在的。

Gossip協議本質上是平均每個節點的資源消耗,能夠保證每個節點資源的消耗只會隨著總節點數的增多而緩慢增長。考慮一種Push方案,每個節點在收到其它節點發送的訊息時,立刻隨機向其它固定數量的節點轉發這個訊息,例如向4個隨機節點發送這個訊息。即便沒有仔細的計算,我們依然可以直觀的感覺到這個簡單的方案是可以滿足平均資源消耗的目的的。但是,隨著需要廣播的資料越來越多,每次廣播一個訊息,整體來看,寬頻的佔用提高了4倍。這會導致節點間會充斥著已經被充分廣播的舊訊息。

Push & Pull & Anti-tropy 機制

上文的簡單Push方案沒有一個終止機制,隨著需要廣播的資料越來越多,每次廣播一個訊息,寬頻的佔用提高了4倍。這會導致節點間會充斥著已經被充分廣播的舊訊息。

我們可以考慮使用一個Time-To-Live (TTL)方法來提高Push方案的效率。簡要的來說就是可以通過在每個訊息第一次被髮送時,為這個訊息增加一個TTL域。TTL域最開始被設定一個固定的值,例如7。訊息每傳播一次,TTL域就會被減少一次。如果一個節點收到TTL域為1的訊息時,就直接丟棄這個訊息,而不是轉發它。

上圖中初始的TTL為2,每個節點在收到一次訊息後,就轉發給隨機兩個節點。直到所有訊息的TTL值耗盡。此時可能存在還沒有收到訊息的節點。但是當我們增大初始TTL的值時,存在還沒有收到訊息節點的概率會成指數下降。例如在100個節點的網路中,每次轉發給4個節點,初始化TTL為9時,當所有訊息都轉發完成後,存在未收到訊息節點的概率是 \(10^{-6}\) 。所以這種增加了TTL的Push方案是可行的。

然而,考慮到訊息總共需要轉發 \(4^9=262144\) 次!所以這種終止方式會消耗大量的CPU和頻寬。在Fabric專案中,沒有使用這種TTL機制,如果一個節點第二次收到相同的訊息時,就不會轉發這個訊息,如下圖。這樣的設定可以保證一個訊息最多被轉發 \(4*100\) 次,大大減少的資源的佔用。如果一個節點在Push過程結束後仍然沒有收到訊息,它會定期的進行Pull操作,即向隨機的其它節點拉取最近的訊息。

Pull機制讓節點可以主動發現新的塊。節點會在本地設定一個定時迴圈發起的請求,每次都隨機選擇多個本地列表中的其它節點。首先,對每個其它節點發送一個 Hello 訊息,等待節點回應本地訊息的摘要集合 Digest ,比如本地區塊的高度。在收到每個其它節點返回的摘要後,節點會將本地摘要與這種訊息進行對比,得到那些本地沒有的訊息,然後向其它節點發送一個 Request 訊息,等待迴應具體的訊息值 Response ,如下圖(來自 註釋 )。

Fabric通過Push和定期的Pull操作保證一個訊息可以以較低的資源佔用和效率,可靠的廣播給所有節點。然而,Pull操作中 Digest 需要包含自賬本建立以來的所有區塊摘要,儘管摘要可能僅僅是區塊的序號,但隨著賬本高度的增加,這一步驟會消耗一定的寬頻。並且,在大多數情況下,並不需要同步比較舊的區塊,所以僅僅需要在記憶體中儲存一定數量的區塊快取即可。Fabric通過設定 peer.gossip.maxBlockCountToStore 來設定快取的最大區塊數量,預設是 10

對於那些離線時間較長或者新加入的節點,Fabric使用一種恢復機制,叫做反熵(Anti-entropy)。反熵機制允許節點任意的選擇缺少區塊編號,向其它節點查詢。通過配置,節點可以設定是否開啟Anti-entropy機制,例如在穩定的網路中,可以不開啟Anti-entropy。如果開啟Anti-entropy機制,每隔固定的時間,節點就會觀察通道上其它的節點,它會選擇一個擁有最高賬本的節點,請求連續的缺失區塊。在Fabric中,預設查詢間隔是 10 秒。

主節點選舉機制

主節點選舉演算法的目標是在一組節點中選舉出一個節點作為主節點,完成特殊的任務。在Fabric Gossip演算法中,主節點需要負責從排序節點接收區塊,然後廣播給組織中其它節點。一個簡單的選舉方式就是推選ID或者網路地址中最小/最大的節點為主節點。但是在群組中的節點可能會隨時加入或者退出網路,同時節點間的訊息也存在丟失的可能,這就需要一種同步機制來保證主節點儘可能的保證唯一。

下面給出一個簡單的主節點選舉演算法,每個節點在成為主節點後立刻向其它節點發送一個宣告"I am leader",同時設定一個定時器來提醒自己定時的傳送這個宣告。如果一個節點收到來自其它節點的宣告,那麼就重新設定Follower的定時器和停止Leader定時器。如果節點沒有收到來自其它節點的宣告並且Follower定時器過期了,那麼該節點就推舉自己為主節點。Follower定時器和Leader定時器過期時節點的行為是一致的,所以在下面的演算法中通過一個條件分支來表達。

LeaderElection(LeaderTime, FollowerTime):
	LeaderAnnouce(LeaderTime)
	While do:
		if receive annouce(msg) && msg.ID >= leader:
			set follower timer with "FollowerTime"
			clear leader timer
			leader = msg.ID
		if leader/follower timer expired:
			LeaderAnnouce(LeaderTime)
LeaderAnnouce(LeaderTime):
	leader = me.ID
	broadcast annouce "I am leader"
	set leader timer with "LeaderTime"

上述演算法有一個小問題,就是當在所有節點同時開始演算法時或者網路分割槽消失時,如何避免多次的主節點轉換。具體來說,假設節點1、節點2、節點3同時開始演算法,它們首先都假定自己是主節點,同時傳送自己是主節點的宣告。節點3首先受到節點2傳送的宣告,觀察到節點2的ID比自身ID小,所以設定主節點為節點2。然後,節點3又收到節點1的宣告,同樣的又設定主節點為節點1。

主節點選舉演算法的目的是從一個群組中選舉出一個節點作為主節點,儘管過程中可能出現多個節點,但我們希望這個過程是收斂的,即任意時刻群組中主節點的數量越少越好。在多次主節點轉換問題的解決上,有以下兩種簡單的改進方法:

  • 增加一個Propose階段,廣播自己想要成為主節點的意願,同時收集其它節點的Propose,最後選擇ID最小的節點作為主節點。
  • 隨機等待一段時間,即每個想要成為主節點的節點先隨機等待一段時間再發送成為主節點的宣告,這樣可以在概率上保證同一時刻想要成為主節點的節點不會太多。

Fabric採取了第一種方法來解決問題。但實際上,主節點選舉演算法是分散式系統中基礎問題,有很多廣泛的研究和解決方案,這裡只是做一些微小的分析。

2. 資料結構

Gossip的傳輸的資料格式被定義在對應的 message.proto 格式檔案 中。這個檔案主要是定義了 Gossip Service 的RPC格式和 Gossip Message 的資料報文格式。

Gossip Service

// Gossip
service Gossip {

  // GossipStream is the gRPC stream used for sending and receiving messages
  rpc GossipStream (stream Envelope) returns (stream Envelope);

  // Ping is used to probe a remote peer's aliveness
  rpc Ping (Empty) returns (Empty);
}

GossipStream 能接收一個流式引數,並返回一個流式值。它們的型別都是 Envelope ,它是對 Gossip Message 資料的一種封裝,包括了原資料和傳送者對其的簽名。 Ping 函式則僅僅用於測試遠端節點的可用性。

Gossip Message

下圖是 Gossip Message 主要的資料結構,其中 channel 指訊息所在的通道, tag 是指訊息傳播的策略,例如僅僅組織內傳播或者通道內傳播。Gossip服務通過這兩個域來控制 Gossip Message 廣播的範圍,達到鏈間的資料訪問控制,保護使用者的隱私。 contentGossip 的核心資料結構, oneof 是protobuf的一個關鍵字,類似於C語言中的聯合體,在 oneof 關鍵字定義的資料結構中,共同使用 content 域。

syntax = "proto3";

message GossipMessage {
    bytes channel = 2;
    Tag tag = 3;
    oneof content {
      ...
    }
}
enum Tag {
    UNDEFINED    = 0;
    EMPTY        = 1;
    ORG_ONLY     = 2;
    CHAN_ONLY    = 3;
    CHAN_AND_ORG = 4;
    CHAN_OR_ORG  = 5;
}

content 定義了21種資料結構,理解它們將會加深對Fabric中Gossip服務整體的理解。根據第節中的分析,其實我們已經可以猜測出很多資料格式的用途。例如:

DataMessage
GossipHello
StateInfo
RemotePvtDataRequest

這裡我們並不會詳細解釋每個資料,但這些訊息包括了Gossip模組的所有功能。

// Membership
AliveMessage alive_msg = 5;
MembershipRequest mem_req = 6;
MembershipResponse mem_res = 7;

// Contains a ledger block
DataMessage data_msg = 8;

// Used for push&pull
GossipHello hello = 9;
DataDigest  data_dig = 10;
DataRequest data_req = 11;
DataUpdate  data_update = 12;

// Empty message, used for pinging
Empty empty = 13;

// ConnEstablish, used for establishing a connection
ConnEstablish conn = 14;

// Used for relaying information
// about state
StateInfo state_info = 15;

// Used for sending sets of StateInfo messages
StateInfoSnapshot state_snapshot = 16;

// Used for asking for StateInfoSnapshots
StateInfoPullRequest state_info_pull_req = 17;

//  Used to ask from a remote peer a set of blocks
RemoteStateRequest state_request = 18;

// Used to send a set of blocks to a remote peer
RemoteStateResponse state_response = 19;

// Used to indicate intent of peer to become leader
LeadershipMessage leadership_msg = 20;

// Used to learn of a peer's certificate
PeerIdentity peer_identity = 21;

Acknowledgement ack = 22;

// Used to request private data
RemotePvtDataRequest privateReq = 23;

// Used to respond to private data requests
RemotePvtDataResponse privateRes = 24;

// Encapsulates private data used to distribute
// private rwset after the endorsement
PrivateDataMessage private_data = 25;

3. 實現

Gossip元件在Fabric中的作用包括了組織內區塊資料交換和私有資料的分發。為了完成組織內區塊資料交換的功能,首先需要在組織內利用主節點選舉機制選出主節點,然後主節點負責與排序節點通訊,獲取區塊。最後,通過資料傳播演算法(包括了Push、Pull和Anti-tropy)將資料廣播給組織內其它所有節點。為了完成私有資料的分發,背書節點在執行完成鏈碼後,將私有資料廣播給自己所在的組織。

節點通訊

Gossip協議通訊實現並不能保證節點間的通訊能達到,是一種盡力而為的通訊方式。Gossip通訊支撐了幾乎Gossip協議的所有基礎功能,包括服務發現、私有資料傳輸和區塊廣播等。

本地節點和遠端節點間通訊使用了gRPC來定義資料傳送的格式:

// Gossip
service Gossip {
    // GossipStream is the gRPC stream used for sending and receiving messages
    rpc GossipStream (stream Envelope) returns (stream Envelope);
    // Ping is used to probe a remote peer's aliveness
    rpc Ping (Empty) returns (Empty);
}

Ping 是用來測試遠端節點是否存活的函式,而 GossipStream 則是實際用來傳輸資料的函式。

當本地節點首次向遠端節點發送訊息時,本地節點會建立並維護一個gRPC連線,維護gRPC連結的資料結構在 gossip/comm/conn.go:connectionStore 中。同樣的,如果接收到了來自遠端節點的連線,也會在 connectionStore 中維護這個連線。這個資料結構保證了針對每一個遠端節點同時只會存在一份 stream

type connectionStore struct {
	...
    pki2Conn         map[string]*connection // connection 是對 stream 讀寫的封裝。
        ...
}

在建立起與遠端節點的連線後,對每一個 connection ,會呼叫 serviceConnection 函式來執行兩個協程,分別負責從 stream 中讀寫資料。

func (conn *connection) serviceConnection() error {
    ...
    go conn.readFromStream(errChan, msgChan)
    go conn.writeToStream()
    ...
}

Comm 介面主要實現幾種與遠端節點通訊的方式,它們的實現並不複雜,這裡著重解釋一下其中的訂閱機制,即從遠端節點接收到訊息後如何出理。訂閱機制的主要實現在 demux.go 檔案中。對外的介面則是 Accept 函式介面。如下所示,它接受一個謂詞 common.MessageAcceptor 來匹配是否是需要的訊息,並返回一個 Channel 來供上層應用接收資訊。

type Comm interface {
    Accept(common.MessageAcceptor) <-chan protoext.ReceivedMessage
}

在具體實現上,會將收到的謂詞加入到 ChannelDeMultiplexer 結構體中,然後等待訊息。

對所有的接收到的訊息,相應的 Connection 會先檢查其是否為 ack 型別的訊息,然後再將訊息釋出到 ChannelDeMultiplexer 結構體中進行檢查。具體實現在 comm_impl.go 中。

h := func(m *protoext.SignedGossipMessage) {
    c.logger.Debug("Got message:", m)
    c.msgPublisher.DeMultiplex(&ReceivedMessageImpl{
    conn:                conn,
    SignedGossipMessage: m,
    connInfo:            connInfo,
    })
}
conn.handler = interceptAcks(h, connInfo.ID, c.pubSub)

在訊息到達 ChannelDeMultiplexer 結構體後,訊息會輪詢所有收集到的 MessageAcceptor 謂詞,然後將滿足謂詞條件的訊息傳送到對應的 Channel 中。

for _, ch := range channels {
	...
    if ch.pred(msg) { // 判斷是否滿足謂詞
        select {
            case <-m.stopCh: // 判斷是否停止
                m.deMuxInProgress.Done()
                return // stopping
            case ch.ch <- msg: // 將謂詞傳送給對應的Channel
        } 
    }
    ...
}

區塊資料傳播

區塊資料傳播的目的是將一個區塊在一個組織內傳播起來。首先,一個組織內選擇主節點,主節點負責連線排序節點,然後將從排序節點獲得的區塊,通過Gossip協議,廣播給組織內其它所有節點。當賬本通道 Channel 建立後,不同組織的節點需要互相通訊,例如廣播私有資料、身份資訊等。這時需要依賴於 anchor node 節點的幫助。在賬本通道建立的時刻,必須至少配置一個 anchor node 在通道初始化的配置交易裡。這樣通過定時連線 anchor node 中繼節點可以獲取其它組織內節點的資訊,隨著時間的延長,所有節點都會得到一個全域性統一的節點檢視。

主節點選舉

選擇一個主節點的演算法已經在第節中進行了介紹,它的實現在 gossip/election 包內。建立主節點選舉功能的程式碼在 NewLeaderElectionService 中。Fabric中每一個的 Channel 的建立都需要初始化一個新的主節點選舉。這裡就不多做介紹,主要是指出一下節點在當選主節點後需要從排序節點收取資料。

注意go語言中的 Channel 是一個用來協程之間共享資料的關鍵字,而Fabric中的 Channel 是指多個組織中形成的賬本通道。

func NewLeaderElectionService(adapter LeaderElectionAdapter, id string, callback leadershipCallback, config ElectionConfig) LeaderElectionService {
    ...
}

adapter 是指 Gossip 服務本身,可以負責在組織內節點間進行資料傳播,而 leadershipCallback 則是任何一個節點在當選主節點或者停止當選主機節點後都會呼叫的函式。該函式在 onStatusChangeFactory 中給出。

func (g *GossipService) onStatusChangeFactory(channelID string, committer blocksprovider.LedgerInfo) func(bool) {
	return func(isLeader bool) {
		if isLeader {
			yield := func() {
				g.lock.RLock()
				le := g.leaderElection[channelID]
				g.lock.RUnlock()
				le.Yield()
			}
			logger.Info("Elected as a leader, starting delivery service for channel", channelID)
			if err := g.deliveryService[channelID].StartDeliverForChannel(channelID, committer, yield); err != nil {
				logger.Errorf("Delivery service is not able to start blocks delivery for chain, due to %+v", err)
			}
		} else {
			logger.Info("Renounced leadership, stopping delivery service for channel", channelID)
			if err := g.deliveryService[channelID].StopDeliverForChannel(channelID); err != nil {
				logger.Errorf("Delivery service is not able to stop blocks delivery for chain, due to %+v", err)
			}
		}
	}
}

可以比較清晰的觀察到當節點當選為主節點後, startDeliverForChannel 開始為當前通道收取區塊資料, stopDeliverForChannel 則是結束從通道收取區塊資料。

Push

Push操作比較簡單,涉及到的資料結構僅僅包括了 DataMessage ,其定義如下:

syntax = "proto3";
// DataMessage is the message that contains a block
message DataMessage {
    Payload payload = 1;
}
// Payload contains a block
message Payload {
  uint64 seq_num              = 1;
  bytes data                  = 2;
  repeated bytes private_data = 3;
}

DataMessage 只有一個域 Payload , Payload 中包含了一個區塊號,序列化後的區塊資料和私有資料三個域。處理 DataMessage 的函式在 HandleMessage 中。跳過一大段驗證的邏輯,當接收到的 msgDataMessage 時,它會做以下操作:

added = gc.blockMsgStore.Add(msg.GetGossipMessage())
if added {
	gc.logger.Debugf("Adding %v to the block puller", msg.GetGossipMessage())
	gc.blocksPuller.Add(msg.GetGossipMessage())
}
...
if added {
	// Forward the message
	gc.Forward(msg)
	// DeMultiplex to local subscribers
	gc.DeMultiplex(m)
}

blockMsgStore 是一個帶時間過期的快取結構,它會在其中的訊息大小超過最大的訊息容量或者訊息 TTL 到期時,移除那些舊訊息。當移除舊訊息時, blockMsgStore 會使用一個回撥函式,將這個舊訊息從 blocksPuller 中刪除。這可以達到 Pull 演算法始終只計算最新的快取訊息。 Forward 函式正如它的名字,轉發它的訊息引數,但它不會立即對訊息進行轉發,而是等待一段時間或者緩衝區滿時,統一隨機選擇節點進行轉發。 DeMultiplex 函式在上一小節中已經介紹,這裡不再贅述。

Pull

Pull演算法的實現被解耦成兩個部分 gossip/gossip/algo/pull.gogossip/gossip/pull/pullstore.gopull.go 中主要是演算法流程的時間,而 pullstore 負責處理資料格式等具體事務。值得注意的是,一個區塊的 Digest 摘要就是指它的區塊號。

Anti-tropy

為了介紹 Anti-tropy 的實現,首先要解釋一下 Gossip 模組是如何與 Ledger 儲存互動的。主要的程式碼在 gossip/state 包內。當一個節點收到一個區塊,它會先觀察到這個區塊是否是比賬本高度正好高一個的區塊,如果是,就立刻進行儲存,如果不是,就先快取在 PayloadsBuffer 這個資料結構中,等待後面用到,這個功能在 Push 函式中被實現。

func (b *PayloadsBufferImpl) Push(payload *proto.Payload) {
	...
        seqNum := payload.SeqNum
	if seqNum < b.next || b.buf[seqNum] != nil {
		b.logger.Debugf("Payload with sequence number = %d has been already processed", payload.SeqNum)
		return
	}
	b.buf[seqNum] = payload
	// Send notification that next sequence has arrived
	if seqNum == b.next && len(b.readyChan) == 0 {
		b.readyChan <- struct{}{} // 傳送已經準備好提交的訊號
	}
}

在準備好區塊資料後,以協程執行的 deliverPayloads 函式會將區塊提交到賬本上。

func (s *GossipStateProviderImpl) deliverPayloads() {
	for {
		select {
		// Wait for notification that next seq has arrived
		case <-s.payloads.Ready():
                        ...
                        err := s.commitBlock(rawBlock, p)
		case <-s.stopCh:
			return
		}
}

antiEntropy 函式也是以協程的方式執行,如下面的程式碼。它會根據 StateCheckInterval 配置定期的檢查其它所有節點的區塊高度,然後自身的區塊不是最新的,就向其它區塊請求最高高度區塊和自身區塊中的所有區塊。

func (s *GossipStateProviderImpl) antiEntropy() {
	defer s.logger.Debug("State Provider stopped, stopping anti entropy procedure.")

	for {
		select {
		case <-s.stopCh:
			return
		case <-time.After(s.config.StateCheckInterval):
			ourHeight, err := s.ledger.LedgerHeight()
			if err != nil {
				// Unable to read from ledger continue to the next round
				s.logger.Errorf("Cannot obtain ledger height, due to %+v", errors.WithStack(err))
				continue
			}
			if ourHeight == 0 {
				s.logger.Error("Ledger reported block height of 0 but this should be impossible")
				continue
			}
			maxHeight := s.maxAvailableLedgerHeight()
			if ourHeight >= maxHeight {
				continue
			}

			s.requestBlocksInRange(uint64(ourHeight), uint64(maxHeight)-1)
		}
	}
}

私有資料傳播

私有資料的傳遞只有兩個操作,一個背書節點在檢查 peer 節點發送的 proposal 時,會將產生的私有資料通過 DistributePrivateData 函式,根據鏈碼的策略廣播給所有相關節點。另外一個是節點可以定時的通過 reconcile 函式從其它節點拉取私有資料。

func (r *Reconciler) run() {
	for {
		select {
		case <-r.stopChan:
			return
		case <-time.After(r.ReconcileSleepInterval):
			r.logger.Debug("Start reconcile missing private info")
			if err := r.reconcile(); err != nil {
				r.logger.Error("Failed to reconcile missing private info, error: ", err.Error())
			}
		}
	}
}

節點通過 fetchPrivateData 函式呼叫 scatterRequestsgatherResponses 兩個函式分別釋出獲取私有資料的請求和接受私有資料的請求。

func (p *puller) fetchPrivateData(dig2Filter digestToFilterMapping) (*privdatacommon.FetchedPvtDataContainer, error) {
    ...
    subscriptions := p.scatterRequests(peer2digests)
    responses := p.gatherResponses(subscriptions)
    ...
}

對於來自其它節點的請求,節點在 handle 函式中分別對 RemotePvtDataRequestRemotePvtDataResponse 兩種型別的訊息進行處理。

func (p *puller) listen() {
	for {
		select {
		case <-p.stopChan:
			return
		case msg := <-p.msgChan:
			if msg == nil {
				// comm module stopped, hence this channel
				// closed
				return
			}
			if msg.GetGossipMessage().GetPrivateRes() != nil {
				p.handleResponse(msg)
			}
			if msg.GetGossipMessage().GetPrivateReq() != nil {
				p.handleRequest(msg)
			}
		}
	}
}

4. 小結

Gossip模組是Fabric執行中節點通訊的重要組成部分,主要有兩個功能,一是從orderer節點接收區塊並傳播,二是通過背書節點對私有資料進行傳播。Gossip通過主節點選取、Push&Pull機制、反熵機制、快取機制等保證了可以高效的傳播資料。本節不僅介紹了Gossip模組的設計原理,還分析了重要資料結構和程式碼實現,幫助我們更好的理解Gossip模組的與逆行原理。

Gossip協議是一個非常經典的分散式協議,它保證廣播操作的可擴充套件性,不會因為節點數量增多而降低效率,已經應用在包括分散式資料庫、區塊鏈等多個領域。同時,Gossip協議在學術上也存在廣泛的研究。本文只是對Fabric中Gossip模組做出簡要的分析,任何錯誤或者不完善的內容都非常歡迎被指出,我將及時修改。

5 參考內容

[1] Randomized Rumor Spreading https://ieeexplore.ieee.org/abstract/document/892324/

[2] Hyperledger fabric dissemination network https://jira.hyperledger.org/secure/attachment/10049/gossipArch.pdf

[3] Gossip data dissemination protocol https://hyperledger-fabric.readthedocs.io/vi/latest/gossip.html

[4] Fair and Efficient Gossip in Hyperledger Fabric https://arxiv.org/pdf/2004.07060

[5] Why multicast protocols (don't) scale: an analysis of multipoint algorithms for scalable group communication, chapter 05 https://thesis.library.caltech.edu/3236/

[6] Architecture Reference Docs: Gossip data dissemination protocol https://hyperledger-fabric.readthedocs.io/en/latest/gossip.html

宣告

本內容並非是面向區塊鏈初學者的,而是假設閱讀者已經瞭解區塊鏈和Hyperledger fabric的基本原理(文件)。

本內容的Hyperledger Fabric的原始碼剖析如果沒有特殊說明,版本為release 2.4,使用Goland開發工具對程式碼進行追蹤。

轉載需要註明出處。