可拔插交易背書和驗證
動機
當交易在提交被驗證時,peer節點在交易本身的狀態改變之前執行各種檢查:
- 驗證簽名交易的標識
- 驗證交易中背書人的簽名
- 確保交易滿足相應鏈碼的命名空間的背書策略
有些用例要求與fabric驗證規則不同的自訂交易驗證規則,例如:
- State-based endorsemet(基於狀態的背書):當背書策略取決於密鑰,並不僅僅取決於命名空間。
- UTXO(Unspent Transaction Output未花費交易輸出):當驗證考慮到,不論交易是否不會對輸入雙花。
- Anonymous transactions(匿名交易):當背書不包含peer節點的身份,但是無法連結到peer節點身份的簽名和公開金鑰被共用。
可拔插背書與驗證邏輯
fabric運行將定製的背書和驗證邏輯實現和部署在peer節點中,以可插拔的方式與鏈碼處理相關聯。這個邏輯不僅可以編譯到peer節點中,內建於可選邏輯中,也可以作為Golang外掛程式與peer節點一起編譯和部署。
回想一下,在鏈碼執行個體化時,每個鏈碼都與其自己的背書和驗證邏輯相關聯。如果使用者未選擇一個,則隱含選取預設的內建邏輯。peer節點管理員通過在peer節點啟動時載入並且應用定製的背書/驗證邏輯來改變通過擴充peer節點本地配置而選擇的背書/驗證邏輯。
配置
每一個peer節點都有一個本地設定檔(core.yaml),它聲明了背書/驗證邏輯名稱和要啟動並執行實現之間的映射關係。
預設的背書邏輯叫做ESCC,預設的驗證邏輯叫做VSCC,他們的定義可以在本地設定檔的"handlers"部分找到:
handlers: endorsers: escc: name: DefaultEndorsement validators: vscc: name: DefaultValidation
當背書或者驗證實現被編譯到peer節點中時,"name"屬性標識要啟動並執行初始化函數,以便獲得建立背書/驗證邏輯執行個體的工程。
該函數是在"core/handlers/library/library.go"路徑下的HandlerLibrary構造的執行個體方法,為了添加自訂背書/驗證邏輯,需要使用任何額外的方法擴充此構造。
由於這很複雜並且較難於部署,因此可以以Golang外掛程式的形式通過在名為"library"屬性名稱下添加另一個屬性來部署自訂的背書/驗證模組。
舉個例子,如果我們以外掛程式的形式實現了自訂的背書和驗證邏輯模組作為基於狀態的背書,我們可以在core.yaml設定檔中以如下形式定義:
handlers: endorsers: escc: name: DefaultEndorsement statebased: name: state_based library: /etc/hyperledger/fabric/plugins/state_based_endorsement.so validators: vscc: name: DefaultValidation statebased: name: state_based library: /etc/hyperledger/fabric/plugins/state_based_validation.so
我們必須將.so外掛程式檔案放在peer節點本地檔案系統中。
此後,自訂背書或者驗證邏輯實現將被稱為"外掛程式",即使它們被編譯到peer節點中。
背書外掛程式實現
為了實現背書外掛程式,必須實現在"core/handlers/endorsement/api/endorsement.go"檔案中相應的外掛程式介面:
// 背書外掛程式提案回複type Plugin interface { //為給定的有效載荷(ProposalResponsePayload位元組)背書籤名,並且可以選擇改變它 // 返回: // 背書:有效載荷上籤名,以及用於驗證簽名的標識。 // 作為輸入提供的有效載荷(可在此功能中修改) // 或者失敗時出錯 Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) // 初始化將依賴注入外掛程式的執行個體 Init(dependencies ...Dependency) error}
通過讓peer節點調用PluginFactory介面中的New方法為每個通道建立給定外掛程式類型(通過方法名稱識別為HandlerLibrary的執行個體方法或者.so外掛程式檔案路徑)的背書外掛程式執行個體,該方法期望由外掛程式開發人員實現:
// PluginFactory 建立一個新的外掛程式執行個體type PluginFactory interface { New() Plugin}
初始化方法被希望接收在"core/handlers/endorsement/api/"路徑下聲明的,識別為嵌入Dependency介面的所有依賴項作為輸入。
在建立外掛程式執行個體之後,peer節點將依賴關係作為傳遞參數調用初始化方法(Init)。
目前,fabric為背書外掛程式提供了一下依賴項:
- SigningIdentityFetcher:返回一個局域給定簽名提案的SigningIdentity執行個體:
// SigningIdentity對訊息進行簽名並將其公用標識序列化為位元組資料type SigningIdentity interface { // Serialize 返回此標識的位元組表示形式,用於驗證此SigningIdentity簽名的訊息 Serialize() ([]byte, error) // Sign 為給定有效載荷簽名並返回簽名 Sign([]byte) ([]byte, error)}
- StateFetcher:擷取與狀態資料庫(world state)互動的狀態物件(State)。
// State 定義與狀態資料的互動方式type State interface { // GetPrivateDataMultipleKeys 在一次調用中擷取多個私人資料項目的值 GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error) // GetStateMultipleKeys 在一次調用中擷取多個鍵的值 GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) // GetTransientByTXID 擷取與給定txID關聯的私人資料值 GetTransientByTXID(txID string) ([]*rwset.TxPvtReadWriteSet, error) // Done 釋放狀態資料佔用的資源 Done() }
驗證外掛程式的實現
為了實現驗證外掛程式,必須實現在路徑"core/handlers/validation/api/validation.go"下的外掛程式介面:
// 驗證交易外掛程式type Plugin interface { // 如果在給定塊中給定位置的交易內給定位置的動作是有效Validate函數返回nil,否則返回一個error錯誤 Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error // 初始化將依賴注入外掛程式的執行個體 Init(dependencies ...Dependency) error}
每個ContextDatum都是由peer節點傳遞給驗證外掛程式的額外的運行時派生的中繼資料。目前,唯一傳遞的ContextDatum是代錶鏈碼的背書策略:
// SerializedPolicy 定義一個序列化策略type SerializedPolicy interface { validation.ContextDatum // Bytes 返回SerializedPolicy位元組形式資料 Bytes() []byte }
通過讓peer節點調用PluginFactory介面中的New方法為每個通道建立給定外掛程式類型(通過方法名稱識別為HandlerLibrary的執行個體方法或者.so外掛程式檔案路徑)的驗證外掛程式執行個體,該方法期望由外掛程式開發人員實現:
// PluginFactory 建立一個新的外掛程式執行個體type PluginFactory interface { New() Plugin}
初始化方法被希望接收在"core/handlers/validation/api/"路徑下聲明的,識別為嵌入Dependency介面的所有依賴項作為輸入。
在建立外掛程式執行個體之後,peer節點將依賴關係作為傳遞參數調用初始化方法(Init)。
目前,fabric為背書外掛程式提供了一下依賴項:
// PolicyEvaluator 評估策略type PolicyEvaluator interface { validation.Dependency // Evaluate 接收一組簽名資料並評估這組簽名是否滿足給定的位元組資料的策略 Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error}
- StateFetcher:擷取與狀態資料庫(world state)互動的狀態物件(State)。
// State 定義與狀態資料的互動方式type State interface { // GetStateMultipleKeys 在一次調用中擷取多個鍵的值 GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) // GetStateRangeScanIterator 返回一個包含給定鍵範圍的所有索引值集合的迭代器。startKey被包含在結果中,並且排除了endKey。空的startKey引用第一個可用鍵,空的endKey引用最後一個可用鍵。為了掃描所有鍵,startKey和endKey都可以作為空白字串提供。但是,出於效能原因,應謹慎使用完整掃描。返回的ResultsIterator包含類型為*KV的結果,該結果定義在"protos/ledger/queryresult" GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error) // Done 釋放狀態資料佔用的資源 Done()}
重要注意事項
- 所有節點驗證外掛程式的一致性:在將來的版本中,fabric通道基礎設施將保證在任何給定的區塊鏈高度,通道中的所有peer節點對給定的鏈碼使用相同的驗證邏輯,以消除可能由於在peer節點之間意外運行不同實現導致狀態差異的錯誤配置的可能性。但是,目前系統才做元和管理員有責任確保不會發生這種情況。
- 驗證外掛程式的錯誤處理:每當驗證外掛程式無法確定由於某些瞬態執行問題(例如,無法訪問資料庫)而無法確定給定交易是否被驗證有效時,它應該返回在"core/handlers/validation/api/validation.go"中定義的ExecutionFailureError類型的錯誤。但是,如果返回ExecutionFailureError錯誤,則鏈處理將暫停,而不是將交易標記為無效。只是為了防止不同peer節點之間的狀態分歧。
- 將fabric代碼匯入外掛程式:非常不鼓勵匯入除了協議之外的fabric代碼作為外掛程式的一部分,這可能在fabric發行版之間發生代碼更改時導致問題,或者在運行不同版本peer節點時導致不可操作性問題。