可拔插交易背書和驗證
動機
當(dāng)交易在提交被驗證時,peer節(jié)點在交易本身的狀態(tài)改變之前執(zhí)行各種檢查:
- 驗證簽名交易的標(biāo)識
- 驗證交易中背書人的簽名
- 確保交易滿足相應(yīng)鏈碼的命名空間的背書策略
有些用例要求與fabric驗證規(guī)則不同的自定義交易驗證規(guī)則往果,例如:
- State-based endorsemet(基于狀態(tài)的背書):當(dāng)背書策略取決于密鑰忘古,并不僅僅取決于命名空間振湾。
- UTXO(Unspent Transaction Output未花費交易輸出):當(dāng)驗證考慮到,不論交易是否不會對輸入雙花。
- Anonymous transactions(匿名交易):當(dāng)背書不包含peer節(jié)點的身份锨匆,但是無法鏈接到peer節(jié)點身份的簽名和公鑰被共享。
可拔插背書與驗證邏輯
fabric運行將定制的背書和驗證邏輯實現(xiàn)和部署在peer節(jié)點中脐嫂,以可插拔的方式與鏈碼處理相關(guān)聯(lián)统刮。這個邏輯不僅可以編譯到peer節(jié)點中,內(nèi)置于可選邏輯中账千,也可以作為Golang插件與peer節(jié)點一起編譯和部署侥蒙。
回想一下,在鏈碼實例化時匀奏,每個鏈碼都與其自己的背書和驗證邏輯相關(guān)聯(lián)鞭衩。如果用戶未選擇一個,則隱式選擇默認的內(nèi)置邏輯娃善。peer節(jié)點管理員通過在peer節(jié)點啟動時加載并且應(yīng)用定制的背書/驗證邏輯來改變通過擴展peer節(jié)點本地配置而選擇的背書/驗證邏輯论衍。
配置
每一個peer節(jié)點都有一個本地配置文件(core.yaml),它聲明了背書/驗證邏輯名稱和要運行的實現(xiàn)之間的映射關(guān)系聚磺。
默認的背書邏輯叫做ESCC坯台,默認的驗證邏輯叫做VSCC,他們的定義可以在本地配置文件的"handlers"部分找到:
handlers:
endorsers:
escc:
name: DefaultEndorsement
validators:
vscc:
name: DefaultValidation
當(dāng)背書或者驗證實現(xiàn)被編譯到peer節(jié)點中時瘫寝,"name"屬性標(biāo)識要運行的初始化函數(shù)蜒蕾,以便獲得創(chuàng)建背書/驗證邏輯實例的工程稠炬。
該函數(shù)是在"core/handlers/library/library.go"路徑下的HandlerLibrary構(gòu)造的實例方法,為了添加自定義背書/驗證邏輯咪啡,需要使用任何額外的方法擴展此構(gòu)造首启。
由于這很復(fù)雜并且較難于部署,因此可以以Golang插件的形式通過在名為"library"屬性名稱下添加另一個屬性來部署自定義的背書/驗證模塊撤摸。
舉個例子毅桃,如果我們以插件的形式實現(xiàn)了自定義的背書和驗證邏輯模塊作為基于狀態(tài)的背書,我們可以在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節(jié)點本地文件系統(tǒng)中准夷。
此后钥飞,自定義背書或者驗證邏輯實現(xiàn)將被稱為"插件",即使它們被編譯到peer節(jié)點中衫嵌。
背書插件實現(xiàn)
為了實現(xiàn)背書插件代承,必須實現(xiàn)在"core/handlers/endorsement/api/endorsement.go"文件中相應(yīng)的插件接口:
// 背書插件提案回復(fù)
type Plugin interface {
//為給定的有效載荷(ProposalResponsePayload字節(jié))背書簽名,并且可以選擇改變它
// 返回:
// 背書:有效載荷上簽名渐扮,以及用于驗證簽名的標(biāo)識论悴。
// 作為輸入提供的有效載荷(可在此功能中修改)
// 或者失敗時出錯
Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error)
// 初始化將依賴注入插件的實例
Init(dependencies ...Dependency) error
}
通過讓peer節(jié)點調(diào)用PluginFactory接口中的New方法為每個通道創(chuàng)建給定插件類型(通過方法名稱識別為HandlerLibrary的實例方法或者.so插件文件路徑)的背書插件實例,該方法期望由插件開發(fā)人員實現(xiàn):
// PluginFactory 創(chuàng)建一個新的插件實例
type PluginFactory interface {
New() Plugin
}
初始化方法被希望接收在"core/handlers/endorsement/api/"路徑下聲明的墓律,識別為嵌入Dependency接口的所有依賴項作為輸入膀估。
在創(chuàng)建插件實例之后,peer節(jié)點將依賴關(guān)系作為傳遞參數(shù)調(diào)用初始化方法(Init)耻讽。
目前察纯,fabric為背書插件提供了一下依賴項:
- SigningIdentityFetcher:返回一個局域給定簽名提案的SigningIdentity實例:
// SigningIdentity對消息進行簽名并將其公共標(biāo)識序列化為字節(jié)數(shù)據(jù)
type SigningIdentity interface {
// Serialize 返回此標(biāo)識的字節(jié)表示形式,用于驗證此SigningIdentity簽名的消息
Serialize() ([]byte, error)
// Sign 為給定有效載荷簽名并返回簽名
Sign([]byte) ([]byte, error)
}
- StateFetcher:獲取與狀態(tài)數(shù)據(jù)庫(world state)交互的狀態(tài)對象(State)针肥。
// State 定義與狀態(tài)數(shù)據(jù)的交互方式
type State interface {
// GetPrivateDataMultipleKeys 在一次調(diào)用中獲取多個私有數(shù)據(jù)項的值
GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error)
// GetStateMultipleKeys 在一次調(diào)用中獲取多個鍵的值
GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error)
// GetTransientByTXID 獲取與給定txID關(guān)聯(lián)的私有數(shù)據(jù)值
GetTransientByTXID(txID string) ([]*rwset.TxPvtReadWriteSet, error)
// Done 釋放狀態(tài)數(shù)據(jù)占用的資源
Done()
}
驗證插件的實現(xiàn)
為了實現(xiàn)驗證插件饼记,必須實現(xiàn)在路徑"core/handlers/validation/api/validation.go"下的插件接口:
// 驗證交易插件
type Plugin interface {
// 如果在給定塊中給定位置的交易內(nèi)給定位置的動作是有效的Validate函數(shù)返回nil,否則返回一個error錯誤
Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error
// 初始化將依賴注入插件的實例
Init(dependencies ...Dependency) error
}
每個ContextDatum都是由peer節(jié)點傳遞給驗證插件的額外的運行時派生的元數(shù)據(jù)慰枕。目前具则,唯一傳遞的ContextDatum是代表鏈碼的背書策略:
// SerializedPolicy 定義一個序列化策略
type SerializedPolicy interface {
validation.ContextDatum
// Bytes 返回SerializedPolicy字節(jié)形式數(shù)據(jù)
Bytes() []byte
}
通過讓peer節(jié)點調(diào)用PluginFactory接口中的New方法為每個通道創(chuàng)建給定插件類型(通過方法名稱識別為HandlerLibrary的實例方法或者.so插件文件路徑)的驗證插件實例,該方法期望由插件開發(fā)人員實現(xiàn):
// PluginFactory 創(chuàng)建一個新的插件實例
type PluginFactory interface {
New() Plugin
}
初始化方法被希望接收在"core/handlers/validation/api/"路徑下聲明的具帮,識別為嵌入Dependency接口的所有依賴項作為輸入博肋。
在創(chuàng)建插件實例之后,peer節(jié)點將依賴關(guān)系作為傳遞參數(shù)調(diào)用初始化方法(Init)蜂厅。
目前匪凡,fabric為背書插件提供了一下依賴項:
IdentityDeserializer:將標(biāo)識身份的byte數(shù)據(jù)轉(zhuǎn)換為可被用于驗證由其簽名的身份對象,并根據(jù)其對應(yīng)的MSP進行驗證掘猿,并查看他們是否滿足給定的MSP Principal(見MSP服務(wù)相關(guān)源碼)病游。完整的規(guī)范被定義在"core/handlers/validation/api/identities/identities.go"。
PolicyEvaluator:評估是否滿足給定的策略:
// PolicyEvaluator 評估策略
type PolicyEvaluator interface {
validation.Dependency
// Evaluate 接收一組簽名數(shù)據(jù)并評估這組簽名是否滿足給定的字節(jié)數(shù)據(jù)的策略
Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error
}
- StateFetcher:獲取與狀態(tài)數(shù)據(jù)庫(world state)交互的狀態(tài)對象(State)稠通。
// State 定義與狀態(tài)數(shù)據(jù)的交互方式
type State interface {
// GetStateMultipleKeys 在一次調(diào)用中獲取多個鍵的值
GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error)
// GetStateRangeScanIterator 返回一個包含給定鍵范圍的所有鍵值集合的迭代器衬衬。startKey被包含在結(jié)果中轻绞,并且排除了endKey∮赌停空的startKey引用第一個可用鍵,空的endKey引用最后一個可用鍵唧龄。為了掃描所有鍵兼砖,startKey和endKey都可以作為空字符串提供。但是既棺,出于性能原因讽挟,應(yīng)謹慎使用完整掃描。返回的ResultsIterator包含類型為*KV的結(jié)果丸冕,該結(jié)果定義在"protos/ledger/queryresult"
GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error)
// Done 釋放狀態(tài)數(shù)據(jù)占用的資源
Done()
}
重要注意事項
- 所有節(jié)點驗證插件的一致性:在將來的版本中耽梅,fabric通道基礎(chǔ)設(shè)施將保證在任何給定的區(qū)塊鏈高度,通道中的所有peer節(jié)點對給定的鏈碼使用相同的驗證邏輯胖烛,以消除可能由于在peer節(jié)點之間意外運行不同實現(xiàn)導(dǎo)致狀態(tài)差異的錯誤配置的可能性眼姐。但是,目前系統(tǒng)才做元和管理員有責(zé)任確保不會發(fā)生這種情況佩番。
- 驗證插件的錯誤處理:每當(dāng)驗證插件無法確定由于某些瞬態(tài)執(zhí)行問題(例如众旗,無法訪問數(shù)據(jù)庫)而無法確定給定交易是否被驗證有效時,它應(yīng)該返回在"core/handlers/validation/api/validation.go"中定義的ExecutionFailureError類型的錯誤趟畏。但是贡歧,如果返回ExecutionFailureError錯誤,則鏈處理將暫停赋秀,而不是將交易標(biāo)記為無效利朵。只是為了防止不同peer節(jié)點之間的狀態(tài)分歧。
- 將fabric代碼導(dǎo)入插件:非常不鼓勵導(dǎo)入除了協(xié)議之外的fabric代碼作為插件的一部分猎莲,這可能在fabric發(fā)行版之間發(fā)生代碼更改時導(dǎo)致問題绍弟,或者在運行不同版本peer節(jié)點時導(dǎo)致不可操作性問題。