深度探索Hyperledger技術與應用之超級帳本的典型交易流程

image

上一篇分享了超級帳本的系統邏輯架構和網絡節點架構,本篇主要分享超級帳本的典型交易流程。html

1

典型交易流程

下圖所示爲Hyperledger Fabric 1.0典型的交易流程圖。算法

image

從上一節的網絡節點架構中,咱們已經瞭解到基於Hyperledger Fabric 1.0的區塊鏈應用中涉及幾個節點角色:應用程序、背書節點、排序服務節點和主節點。在圖3-4中,假定各節點已經提早頒發好證書,且已正常啓動,並加入已經建立好的通道。後面的步驟介紹在已經實例化了的鏈碼通道上從發起一個調用交易到最終記帳的全過程。數據庫

一、建立交易提案併發送給背書節點編程

使用應用程序構造交易提案,SignedProposal的結構以下所示:數組

SignedProposal: {

    ProposalBytes(Proposal): {

        Header: {

            ChannelHeader: {

                Type: "HeaderType_ENDORSER_TRANSACTION",

                TxId: TxId,

                Timestamp: Timestamp,

                ChannelId: ChannelId,

                Extension(ChaincodeHeaderExtension): {

                    PayloadVisibility: PayloadVisibility,

                    ChaincodeId: {

                        Path: Path,

                        Name: Name,

                        Version: Version

                    }

                },

                Epoch: Epoch

            },

            SignatureHeader: {

                Creator: Creator,

                Nonce: Nonce

            }

        },

        Payload: {

            ChaincodeProposalPayload: {

                Input(ChaincodeInvocationSpec): {

                    ChaincodeSpec: {

                        Type: Type,

                        ChaincodeId: {

                            Name: Name

                        },

                        Input(ChaincodeInput): {

                            Args: []

                        }

                    }

                },

                TransientMap: TransientMap

            }

        }

    },

    Signature: Signature

}
複製代碼

咱們來看看上面的結構,SignedProposal是封裝了Proposal的結構,添加了調用者的簽名信息。背書節點會根據簽名信息驗證其是不是一個有效的消息。Proposal由兩個部分組成:消息頭和消息結構。消息結構詳細的解釋參考後面的章節。這裏簡單講一下消息頭。緩存

消息頭(Header)也包含兩項內容。bash

通道頭(ChannelHeader):通道頭包含了與通道和鏈碼調用相關的信息,好比在哪一個通道上調用哪一個版本的鏈碼。TxId是應用程序本地生成的交易號,跟調用者的身份證書相關,能夠避免交易號的衝突,背書節點和記帳節點都會校驗是否存在重複交易。網絡

簽名頭(SignatureHeader):簽名頭包含了調用者的身份證書和一個隨機數,用於消息的有效性校驗。架構

應用程序構造好交易提案請求後,選擇背書節點執行並進行背書籤名。背書節點是鏈碼背書策略裏指定的節點。有一些背書節點是離線的,其餘的背書節點能夠拒絕對交易進行背書,也能夠不背書。應用程序能夠嘗試使用其餘可用的背書節點來知足策略。應用程序以何種順序給背書節點發送背書請求是沒有關係的,正常狀況下背書節點執行後的結果是一致的,只有背書節點對結果的簽名不同。併發

二、背書節點模擬交易並生成背書籤名

背書節點在收到交易提案後會進行一些驗證,包括:

  • 交易提案的格式是否正確;

  • 交易是否提交過(重複攻擊保護);

  • 交易簽名有效(經過 MSP);

  • 交易提案的提交者在當前通道上是否已受權有寫權限。

驗證經過後,背書節點會根據當前帳本數據模擬執行鏈碼中的業務邏輯並生成讀寫集(RwSet),其中包含響應值、讀寫集等。在模擬執行時帳本數據不會更新。然後背書節點對這些讀寫集進行簽名成爲提案響應(Proposal Response),而後返回給應用程序。

ProposalResponse的結構以下:

ProposalResponse: {

    Version: Version,

    Timestamp: Timestamp,

    Response: {

        Status: Status,

        Message: Message,

        Payload: Payload

    },

    Payload(ProposalResponsePayload): {

        ProposalHash: ProposalHash,

        Extension(ChaincodeAction): {

            Results(TxRwSet): {

                NsRwSets(NsRwSet): [

                    NameSpace: NameSpace,

                    KvRwSet: {

                        Reads(KVRead): [

                            Key: Key,

                            Version: {

                                BlockNum: BlockNum,

                                TxNum: TxNum

                            }

                        ],

                        RangeQueriesInfo(RangeQueryInfo): [

                            StartKey: StartKey,

                            EndKey: EndKey,

                            ItrExhausted: ItrExhausted,

                            ReadsInfo: ReadsInfo

                        ],

                        Writes(KVWrite): [

                            Key: Key,

                            IsDelete: IsDelete,

                            Value: Value

                        ]

                    }

                ]

            },

            Events(ChaincodeEvent): {

                ChaincodeId: ChaincodeId,

                TxId: TxId,

                EventName: EventName,

                Payload: Payload

            }

            Response: {

                Status: Status,

                Message: Message,

                Payload: Payload

            },

            ChaincodeId: ChaincodeId

        }

    },

    Endorsement: {

        Endorser: Endorser,

        Signature: Signature

    }

}
複製代碼

返回的ProposalResponse中包含了讀寫集、背書節點簽名以及通道名稱等信息。

三、收集交易的背書

應用程序收到ProposalResponse後會對背書節點簽名進行驗證,全部節點接收到任何消息後都是須要先驗證消息合法性的。若是鏈碼只進行帳本查詢,應用程序會檢查查詢響應,但不會將交易提交給排序服務節點。若是鏈碼對帳本進行Invoke操做,則須提交交易給排序服務進行帳本更新,應用程序會在提交交易前判斷背書策略是否知足。若是應用程序沒有收集到足夠的背書就提交交易了,記帳節點在提交驗證階段會發現交易不能知足背書策略,標記爲無效交易。

如何選擇背書節點呢?目前fabric-sdk-go默認的實現是把配置文件選項channels.mychannel.peers(其中的mychannel須要替換成實際的通道名稱)裏的節點所有添加爲背書節點,須要等待全部背書節點的背書籤名。應用程序等待每一個背書節點執行的超時時間是經過配置文件選項client.peer.timeout.connection設置的,配置文件的示例給出的是3秒,根據實際狀況調整,若是沒有設置就是5秒的默認值。

四、構造交易請求併發送給排序服務節點

應用程序接收到全部的背書節點簽名後,根據背書籤名調用SDK生成交易,廣播給排序服務節點。生成交易的過程比較簡單,確認全部的背書節點的執行結果徹底一致,再將交易提案、提案響應和背書籤名打包生成交易。交易的結構以下:

Envelope: {

    Payload: {

        Header: {

            ChannelHeader: {

                Type: "HeaderType_ENDORSER_TRANSACTION",

                TxId: TxId,

                Timestamp: Timestamp,

                ChannelId: ChannelId,

                Extension(ChaincodeHeaderExtension): {

                    PayloadVisibility: PayloadVisibility,

                    ChaincodeId: {

                        Path: Path,

                        Name: Name,

                        Version: Version

                    }

                },

                Epoch: Epoch

            },

            SignatureHeader: {

                Creator: Creator,

                Nonce: Nonce

            }

        },

        Data(Transaction): {

            TransactionAction: [

                Header(SignatureHeader): {

                    Creator: Creator,

                    Nonce: Nonce

                },

                Payload(ChaincodeActionPayload): {

                    ChaincodeProposalPayload: {

                        Input(ChaincodeInvocationSpec): {

                            ChaincodeSpec: {

                                Type: Type,

                                ChaincodeId: {

                                    Name: Name

                                },

                                Input(ChaincodeInput): {

                                    Args: []

                                }

                            }

                        },

                        TransientMap: nil

                    },

                    Action(ChaincodeEndorsedAction): {

                        Payload(ProposalResponsePayload): {

                            ProposalHash: ProposalHash,

                            Extension(ChaincodeAction): {

                                Results(TxRwSet): {

                                    NsRwSets(NsRwSet): [

                                        NameSpace: NameSpace,

                                        KvRwSet: {

                                            Reads(KVRead): [

                                                Key: Key,

                                                Version: {

                                                    BlockNum: BlockNum,

                                                    TxNum: TxNum

                                                }

                                            ],

                                            RangeQueriesInfo(RangeQueryInfo): [

                                                StartKey: StartKey,

                                                EndKey: EndKey,

                                                ItrExhausted: ItrExhausted,

                                                ReadsInfo: ReadsInfo

                                            ],

                                            Writes(KVWrite): [

                                                Key: Key,

                                                IsDelete: IsDelete,

                                                Value: Value

                                            ]

                                        }

                                    ]

                                },

                                Events(ChaincodeEvent): {

                                    ChaincodeId: ChaincodeId,

                                    TxId: TxId,

                                    EventName: EventName,

                                    Payload: Payload

                                }

                                Response: {

                                    Status: Status,

                                    Message: Message,

                                    Payload: Payload

                                },

                                ChaincodeId: ChaincodeId

                            }

                        },

                        Endorsement: [

                            Endorser: Endorser,

                            Signature: Signature

                        ]

                    }

                }

            ]

        }

    },

    Signature: Signature

}
複製代碼

咱們來看交易信封的幾個對應關係:

  • Envelope.Payload.Header同交易提案SignedProposal.Proposal.Header;

  • Envelope.Payload.Data.TransactionAction.Header是交易提案的提交者的身份信息,同SignedProposal.Proposal.Header.SignatureHeader和Envelope.Payload.Header.SignatureHeader是冗餘的;

  • Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload同交易提案的SignedProposal.Proposal.Payload.ChaincodeProposalPayload,惟一不一樣的是,TransientMap強制設置爲nil,目的是避免在區塊中出現一些敏感信息;

  • Envelope.Payload.Data.TransactionAction.Payload.Action.Payload結構,其實和Proposal

  • Response.Payload結構徹底同樣;

  • Envelope.Payload.Data.TransactionAction.Payload.Action.Endorsement變成了數組,表明多個背書節點的背書籤名。

整個信封Envelope的Signature是交易提交者對整個Envelope.Payload的簽名。應用程序能夠把生成的交易信封內容發送給任意選擇的幾個排序服務節點。

五、排序服務節點以對交易進行排序並生成區塊

排序服務不讀取交易的內容,若是在生成交易信封內容的時候僞造了交易模擬執行的結果,排序服務節點也不會發現,但會在最終的交易驗證階段校驗出來並標記爲無效交易。排序服務要作得很簡單,先是接收網絡中全部通道發出的交易信息,讀取交易信封的Envelope.Payload.Header.ChannelHeader.ChannelId以獲取通道名稱,按各個通道上交易的接收時間順序對交易信息進行排序,生成區塊。

六、排序服務節點以廣播給組織的主節點

排序服務節點生成區塊之後會廣播給通道上不一樣組織的主節點。

七、記帳節點驗證區塊內容並寫入區塊

背書節點是動態角色,只要參與交易的背書就是背書節點,哪些交易選擇哪些節點做爲背書節點是由應用程序選擇的,這須要知足背書策略才能生效。全部的背書節點都屬於記帳節點。全部的Peer節點都是記帳節點,記錄的是節點已加入通道的帳本數據。記帳節點接收到的是排序服務節點生成的區塊,驗證區塊交易的有效性,提交到本地帳本後再產生一個生成區塊的事件,監聽區塊事件的應用程序能夠進行後續的處理。

若是接收到的區塊是配置區塊,則會更新緩存的配置信息。記帳節點的處理流程如圖所示。

image

交易數據的驗證

區塊數據的驗證是以交易驗證爲單位的,每次對區塊進行驗證時都會生成一個交易號的位圖TxValidationFlags,它記錄每一個交易號的交易驗證狀態,只有狀態爲TxValidationCode_VALID纔是有效的。位圖也會寫入到區塊的元數據BlockMetadataIndex_TRANSACTIONS_FILTER中。交易驗證的時候會檢查如下內容:

  • 是否爲合法的交易:交易格式是否正確,是否有合法的簽名,交易內容是否被篡改;

  • 記帳節點是否加入了這個通道。

基本的驗證經過之後會提交給VSCC進行背書策略的驗證。

記帳節點與VSCC

鏈碼的交易是隔離的,每一個交易的模擬執行結果讀寫集TxRwSet都包含了交易所屬的鏈碼。爲了不錯誤地更新鏈碼交易數據,在交易提交給系統鏈碼VSCC驗證交易內容以前,還會對鏈碼進行校驗。下面這些交易都是非法的:

  • 鏈碼的名稱或者版本爲空;

  • 交易消息頭裏的鏈碼名稱Envelope.Payload.Header.ChannelHeader.Extension.ChaincodeId. Name和交易數據裏的鏈碼名稱Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload.Input.ChaincodeSpec.ChaincodeId.Name不一致;

  • 鏈碼更新當前鏈碼數據時,生成讀寫集的鏈碼版本不是LSCC記錄的最新版本;

  • 應用程序鏈碼更新了LSCC(生命週期管理系統鏈碼)的數據;

  • 應用程序鏈碼更新了不可被外部調用的系統鏈碼的數據;

  • 應用程序鏈碼更新了不可被其餘鏈碼調用的系統鏈碼的數據;

  • 調用了不可被外部調用的系統鏈碼。

基於狀態數據的驗證和MVCC檢查

交易經過VSCC檢查之後,就進入記帳流程。kvledger還會對讀寫集TxRwSet進行MVCC(Multi-Version Concurrency Control)檢查。

kvledger實現的是基於鍵值對(key-value)的狀態數據模型。對狀態數據的鍵有3種操做:

  • 讀狀態數據;

  • 寫狀態數據;

  • 刪除狀態數據。

對狀態數據的讀操做有兩種形式:

  • 基於單一鍵的讀取;

  • 基於鍵範圍的讀取。

MVCC檢查只對讀數據進行校驗,基本邏輯是對模擬執行時狀態數據的版本和提交交易時狀態數據的版本進行比較。若是數據版本發生變化或者某個鍵的範圍數據發生變化,就說明這段時間以內有別的交易改變了狀態數據,當前交易基於原有狀態的處理就是有問題的。因爲交易提交是並行的,因此在交易未打包生成區塊以前,並不能肯定最終的執行順序。若是交易執行的順序存在依賴,在MVCC檢查的時候就會出現依賴的狀態發生了變化,其實是數據出現了衝突。圖所示爲基於狀態的數據驗證的流程圖。

image

寫集合自己包含了寫和刪除的數據,有一個狀態位標識是否刪除數據。爲了提高效率,狀態數據庫的提交是批處理的,整個區塊交易的狀態數據同時提交,這也保證了整個區塊的狀態數據要麼都提交成功,要麼都提交失敗。這時只會出現記錄的帳本數據和狀態數據庫不一致,不會出現區塊的狀態數據不一致的狀況。當帳本數據和狀態數據庫不一致時,能夠經過狀態數據庫的檢查點來標記。

無效交易的處理

僞造的交易會致使無效交易,正常的交易也可能出現無效交易。MVCC檢查的是背書節點在模擬執行的時候,環境是否和記帳節點提交交易時的環境一致,這裏的環境是指狀態數據庫裏數據的三元組(key、value、version)是否徹底一致。若是正常提交的交易在這個過程當中涉及的數據發生了變化,那麼也會出現檢查失敗從而致使無效交易。在這種狀況下,須要在上層的應用程序有一些補償措施,好比調整交易打包的配置,從新提交失敗的交易等。

在目前版本的實現中,無效交易也會保留在區塊中,能夠經過區塊記錄的元數據肯定哪些是無效交易。無效交易的讀寫集不會提交到狀態數據庫中,不會致使狀態數據庫發生變化,只是會佔用區塊的大小,佔用記帳節點的硬盤空間。後續的版本會實現帳本的精簡,過濾掉無效交易。

八、在組織內部同步最新的區塊

下篇預告:深度探索Hyperledger技術與應用之超級帳本的策略管理

京東有購:https://item.jd.com/12279369.html

image
深度探索區塊鏈 A.jpg

深度探索區塊鏈

Hyperledger技術與應用

區塊鏈

張增駿,董寧,朱軒彤,陳劍雄  著

本書由超級帳本執行董事Brian Behlendorf領銜推薦,區塊鏈一線落地實踐團隊、Hyperleger會員智鏈骨幹團對撰寫。深刻講解Hyperledger Fabric 1.0的架構、執行邏輯、核心功能實現、從零部署,並以票據案例爲例,講解具體開發實踐,穿插開發所需的最佳實踐和遇到的問題解決。

機械工業

出版社

image

華章科技是機械出版社的旗下品牌,出版了「計算機科學叢書」等近30個經典套系,在各個細分領域均處於領導地位,其中《Java編程思想》、《算法導論》、《編譯原理》、《數據挖掘:概念與技術》、《深刻理解計算機系統》、《深刻理解Java虛擬機》等著做猶如計算機圖書領域的璀璨明珠,長銷不衰!

image

HiBlock秉承開放、協做、透明、連接、分享的價值觀,致力打造區塊鏈開發者社區。專一於在開發者中推廣區塊鏈,幫助開發者真正掌握區塊鏈技術和應用。

本文內容節選自《深度探索區塊鏈:Hyperledger技術與應用》一書的第3章《超級帳本的系統架構》。

本書做者:張增駿,董寧,朱軒彤,陳劍雄

感謝機械工業出版社華章分社的支持和分享。

活動推薦

**主題:**5月25-27日,Blockathon2018北京站,招募100名開發者一塊兒挑戰區塊鏈開發。

開發者免費,報名需審覈。識別下圖二維碼或點擊「閱讀原文」便可報名參加。

image

點擊「閱讀原文」便可報名。

相關文章
相關標籤/搜索