可拔插交易背書和驗(yàn)證
動(dòng)機(jī)
當(dāng)交易在提交被驗(yàn)證時(shí),peer節(jié)點(diǎn)在交易本身的狀態(tài)改變之前執(zhí)行各種檢查:
- 驗(yàn)證簽名交易的標(biāo)識(shí)
- 驗(yàn)證交易中背書人的簽名
- 確保交易滿足相應(yīng)鏈碼的命名空間的背書策略
有些用例要求與fabric驗(yàn)證規(guī)則不同的自定義交易驗(yàn)證規(guī)則,例如:
- State-based endorsemet(基于狀態(tài)的背書):當(dāng)背書策略取決于密鑰,并不僅僅取決于命名空間。
- UTXO(Unspent Transaction Output未花費(fèi)交易輸出):當(dāng)驗(yàn)證考慮到,不論交易是否不會(huì)對(duì)輸入雙花。
- Anonymous transactions(匿名交易):當(dāng)背書不包含peer節(jié)點(diǎn)的身份,但是無(wú)法鏈接到peer節(jié)點(diǎn)身份的簽名和公鑰被共享。
可拔插背書與驗(yàn)證邏輯
fabric運(yùn)行將定制的背書和驗(yàn)證邏輯實(shí)現(xiàn)和部署在peer節(jié)點(diǎn)中,以可插拔的方式與鏈碼處理相關(guān)聯(lián)。這個(gè)邏輯不僅可以編譯到peer節(jié)點(diǎn)中,內(nèi)置于可選邏輯中,也可以作為Golang插件與peer節(jié)點(diǎn)一起編譯和部署。
回想一下,在鏈碼實(shí)例化時(shí),每個(gè)鏈碼都與其自己的背書和驗(yàn)證邏輯相關(guān)聯(lián)。如果用戶未選擇一個(gè),則隱式選擇默認(rèn)的內(nèi)置邏輯。peer節(jié)點(diǎn)管理員通過(guò)在peer節(jié)點(diǎn)啟動(dòng)時(shí)加載并且應(yīng)用定制的背書/驗(yàn)證邏輯來(lái)改變通過(guò)擴(kuò)展peer節(jié)點(diǎn)本地配置而選擇的背書/驗(yàn)證邏輯。
配置
每一個(gè)peer節(jié)點(diǎn)都有一個(gè)本地配置文件(core.yaml),它聲明了背書/驗(yàn)證邏輯名稱和要運(yùn)行的實(shí)現(xiàn)之間的映射關(guān)系。
默認(rèn)的背書邏輯叫做ESCC,默認(rèn)的驗(yàn)證邏輯叫做VSCC,他們的定義可以在本地配置文件的"handlers"部分找到:
handlers:
endorsers:
escc:
name: DefaultEndorsement
validators:
vscc:
name: DefaultValidation
當(dāng)背書或者驗(yàn)證實(shí)現(xiàn)被編譯到peer節(jié)點(diǎn)中時(shí),"name"屬性標(biāo)識(shí)要運(yùn)行的初始化函數(shù),以便獲得創(chuàng)建背書/驗(yàn)證邏輯實(shí)例的工程。
該函數(shù)是在"core/handlers/library/library.go"路徑下的HandlerLibrary構(gòu)造的實(shí)例方法,為了添加自定義背書/驗(yàn)證邏輯,需要使用任何額外的方法擴(kuò)展此構(gòu)造。
由于這很復(fù)雜并且較難于部署,因此可以以Golang插件的形式通過(guò)在名為"library"屬性名稱下添加另一個(gè)屬性來(lái)部署自定義的背書/驗(yàn)證模塊。
舉個(gè)例子,如果我們以插件的形式實(shí)現(xiàn)了自定義的背書和驗(yà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é)點(diǎn)本地文件系統(tǒng)中。
此后,自定義背書或者驗(yàn)證邏輯實(shí)現(xiàn)將被稱為"插件",即使它們被編譯到peer節(jié)點(diǎn)中。
背書插件實(shí)現(xiàn)
為了實(shí)現(xiàn)背書插件,必須實(shí)現(xiàn)在"core/handlers/endorsement/api/endorsement.go"文件中相應(yīng)的插件接口:
// 背書插件提案回復(fù)
type Plugin interface {
//為給定的有效載荷(ProposalResponsePayload字節(jié))背書簽名,并且可以選擇改變它
// 返回:
// 背書:有效載荷上簽名,以及用于驗(yàn)證簽名的標(biāo)識(shí)。
// 作為輸入提供的有效載荷(可在此功能中修改)
// 或者失敗時(shí)出錯(cuò)
Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error)
// 初始化將依賴注入插件的實(shí)例
Init(dependencies ...Dependency) error
}
通過(guò)讓peer節(jié)點(diǎn)調(diào)用PluginFactory接口中的New方法為每個(gè)通道創(chuàng)建給定插件類型(通過(guò)方法名稱識(shí)別為HandlerLibrary的實(shí)例方法或者.so插件文件路徑)的背書插件實(shí)例,該方法期望由插件開(kāi)發(fā)人員實(shí)現(xiàn):
// PluginFactory 創(chuàng)建一個(gè)新的插件實(shí)例
type PluginFactory interface {
New() Plugin
}
初始化方法被希望接收在"core/handlers/endorsement/api/"路徑下聲明的,識(shí)別為嵌入Dependency接口的所有依賴項(xiàng)作為輸入。
在創(chuàng)建插件實(shí)例之后,peer節(jié)點(diǎn)將依賴關(guān)系作為傳遞參數(shù)調(diào)用初始化方法(Init)。
目前,fabric為背書插件提供了一下依賴項(xiàng):
- SigningIdentityFetcher:返回一個(gè)局域給定簽名提案的SigningIdentity實(shí)例:
// SigningIdentity對(duì)消息進(jìn)行簽名并將其公共標(biāo)識(shí)序列化為字節(jié)數(shù)據(jù)
type SigningIdentity interface {
// Serialize 返回此標(biāo)識(shí)的字節(jié)表示形式,用于驗(yàn)證此SigningIdentity簽名的消息
Serialize() ([]byte, error)
// Sign 為給定有效載荷簽名并返回簽名
Sign([]byte) ([]byte, error)
}
- StateFetcher:獲取與狀態(tài)數(shù)據(jù)庫(kù)(world state)交互的狀態(tài)對(duì)象(State)。
// State 定義與狀態(tài)數(shù)據(jù)的交互方式
type State interface {
// GetPrivateDataMultipleKeys 在一次調(diào)用中獲取多個(gè)私有數(shù)據(jù)項(xiàng)的值
GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error)
// GetStateMultipleKeys 在一次調(diào)用中獲取多個(gè)鍵的值
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()
}
驗(yàn)證插件的實(shí)現(xiàn)
為了實(shí)現(xiàn)驗(yàn)證插件,必須實(shí)現(xiàn)在路徑"core/handlers/validation/api/validation.go"下的插件接口:
// 驗(yàn)證交易插件
type Plugin interface {
// 如果在給定塊中給定位置的交易內(nèi)給定位置的動(dòng)作是有效的Validate函數(shù)返回nil,否則返回一個(gè)error錯(cuò)誤
Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error
// 初始化將依賴注入插件的實(shí)例
Init(dependencies ...Dependency) error
}
每個(gè)ContextDatum都是由peer節(jié)點(diǎn)傳遞給驗(yàn)證插件的額外的運(yùn)行時(shí)派生的元數(shù)據(jù)。目前,唯一傳遞的ContextDatum是代表鏈碼的背書策略:
// SerializedPolicy 定義一個(gè)序列化策略
type SerializedPolicy interface {
validation.ContextDatum
// Bytes 返回SerializedPolicy字節(jié)形式數(shù)據(jù)
Bytes() []byte
}
通過(guò)讓peer節(jié)點(diǎn)調(diào)用PluginFactory接口中的New方法為每個(gè)通道創(chuàng)建給定插件類型(通過(guò)方法名稱識(shí)別為HandlerLibrary的實(shí)例方法或者.so插件文件路徑)的驗(yàn)證插件實(shí)例,該方法期望由插件開(kāi)發(fā)人員實(shí)現(xiàn):
// PluginFactory 創(chuàng)建一個(gè)新的插件實(shí)例
type PluginFactory interface {
New() Plugin
}
初始化方法被希望接收在"core/handlers/validation/api/"路徑下聲明的,識(shí)別為嵌入Dependency接口的所有依賴項(xiàng)作為輸入。
在創(chuàng)建插件實(shí)例之后,peer節(jié)點(diǎn)將依賴關(guān)系作為傳遞參數(shù)調(diào)用初始化方法(Init)。
目前,fabric為背書插件提供了一下依賴項(xiàng):
IdentityDeserializer:將標(biāo)識(shí)身份的byte數(shù)據(jù)轉(zhuǎn)換為可被用于驗(yàn)證由其簽名的身份對(duì)象,并根據(jù)其對(duì)應(yīng)的MSP進(jìn)行驗(yàn)證,并查看他們是否滿足給定的MSP Principal(見(jiàn)MSP服務(wù)相關(guān)源碼)。完整的規(guī)范被定義在"core/handlers/validation/api/identities/identities.go"。
PolicyEvaluator:評(píng)估是否滿足給定的策略:
// PolicyEvaluator 評(píng)估策略
type PolicyEvaluator interface {
validation.Dependency
// Evaluate 接收一組簽名數(shù)據(jù)并評(píng)估這組簽名是否滿足給定的字節(jié)數(shù)據(jù)的策略
Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error
}
- StateFetcher:獲取與狀態(tài)數(shù)據(jù)庫(kù)(world state)交互的狀態(tài)對(duì)象(State)。
// State 定義與狀態(tài)數(shù)據(jù)的交互方式
type State interface {
// GetStateMultipleKeys 在一次調(diào)用中獲取多個(gè)鍵的值
GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error)
// GetStateRangeScanIterator 返回一個(gè)包含給定鍵范圍的所有鍵值集合的迭代器。startKey被包含在結(jié)果中,并且排除了endKey。空的startKey引用第一個(gè)可用鍵,空的endKey引用最后一個(gè)可用鍵。為了掃描所有鍵,startKey和endKey都可以作為空字符串提供。但是,出于性能原因,應(yīng)謹(jǐn)慎使用完整掃描。返回的ResultsIterator包含類型為*KV的結(jié)果,該結(jié)果定義在"protos/ledger/queryresult"
GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error)
// Done 釋放狀態(tài)數(shù)據(jù)占用的資源
Done()
}
重要注意事項(xiàng)
- 所有節(jié)點(diǎn)驗(yàn)證插件的一致性:在將來(lái)的版本中,fabric通道基礎(chǔ)設(shè)施將保證在任何給定的區(qū)塊鏈高度,通道中的所有peer節(jié)點(diǎn)對(duì)給定的鏈碼使用相同的驗(yàn)證邏輯,以消除可能由于在peer節(jié)點(diǎn)之間意外運(yùn)行不同實(shí)現(xiàn)導(dǎo)致?tīng)顟B(tài)差異的錯(cuò)誤配置的可能性。但是,目前系統(tǒng)才做元和管理員有責(zé)任確保不會(huì)發(fā)生這種情況。
- 驗(yàn)證插件的錯(cuò)誤處理:每當(dāng)驗(yàn)證插件無(wú)法確定由于某些瞬態(tài)執(zhí)行問(wèn)題(例如,無(wú)法訪問(wèn)數(shù)據(jù)庫(kù))而無(wú)法確定給定交易是否被驗(yàn)證有效時(shí),它應(yīng)該返回在"core/handlers/validation/api/validation.go"中定義的ExecutionFailureError類型的錯(cuò)誤。但是,如果返回ExecutionFailureError錯(cuò)誤,則鏈處理將暫停,而不是將交易標(biāo)記為無(wú)效。只是為了防止不同peer節(jié)點(diǎn)之間的狀態(tài)分歧。
- 將fabric代碼導(dǎo)入插件:非常不鼓勵(lì)導(dǎo)入除了協(xié)議之外的fabric代碼作為插件的一部分,這可能在fabric發(fā)行版之間發(fā)生代碼更改時(shí)導(dǎo)致問(wèn)題,或者在運(yùn)行不同版本peer節(jié)點(diǎn)時(shí)導(dǎo)致不可操作性問(wèn)題。