fabric2.3版本源码记录_1


fabrci源码记录

writer:布羽

说明

本文件中的源码基于fabric2.3.x

阅读源码借鉴《Hyperledger Fabric核心技术》,书本基于v1.4

部分其他资料搜集于博客

源码简拼

MSP:Membership service provider 会员服务提供者
BCCSP:blockchain(前两个字母BC) cryptographic service provider 区域链加密服务提供者
ab:atomic broadcast原子(操作)广播
lscc:lifecycle(L) system(S) chaincode(CC)生命周期系统链码
Spec:Specification,规格标准,详细说明
KV:key-value 键-值
CDS:ChaincodeDeploymentSpec
CIS:ChaincodeInvocationSpec
mgmt:management
SW:software-based
AB:AtomicBroadcast
GB:genesis block,创世纪的block,也就是区域链中的第一个块
CC或cc:chaincode
SCC或scc:system chaincode
cscc:configer system chaincode
lscc:lifecycle system chaincode
escc:endorser system chaincode
vscc:validator system chaincode
qscc:querier system chaincode
alg:algorithm 算法
mcs:mspMessageCryptoService
mock:假装,学样子,模仿的意思,基本上是服务于xxx_test.go的,即用于测试的
Gossip:一种使分布结点达到状态最终一致的算法
attr:attribute
FsBlockStore:file system block store
vdb:versioned database 也就是状态数据库
RTEnv:runtime environment运行环境
pkcs11:pcks#11,一种公匙加密标准,有一套叫做Cryptoki的接口,是一组平台设备无关的API
MCS:mspMessageCryptoService,消息加密服务
sa:SecurityAdvisor
impl:implement,好多处XXX.go和XXXimpl.go是对应的,前者是用于接口或者定义的,后者是实现该接口或定义的
FSM:finite state machine 有限状态机
FS:filesystem 文件系统
blk:block
cli:command line interface 命令行界面
CFG:FABRIC_CFG_PATH中的,应该是config的意思
mgr:manager
cpinfo:checkpoint information,检查点信息
DevMode:development mode,开发模式
Reg:register,注册,登记
hdr:header
impl:implement
oid:ObjectIdentifier,对象标识符
ou或OU:organizational unit
CRL:certificate revocation list,废除证书列表
prop:proposal,申请,交易所发送的申请
ACL:Access Control List,访问控制列表
rwset:read/write set,读写集
tx,Tx:transaction,交易
CSP:cryptographic service provider,BCCSP的后三个字母,加密服务提供者
opt:option,选项
opts:options,多个选项
SKI:当前证书标识,所谓标识,一般是对公匙进行一下hash
AKI:签署方的SKI,也就是签署方的公匙标识
HSM:Hardware Security Modules
ks:KeyStore,Key存储,这个key指的是用于签名的公匙私匙
oid:OBJECT IDENTIFIER,对象身份标识

源码惯例

  • common目录是其所在的层级中的公用的代码。A/common,则说明该common中的代码在A范围中公用,A/B/C/common,则说明该common中的代码在C目录中公用。
  • mock目录是用于方便go测试文件(即众多的XXX_test.go)中进行测试所需要的模拟数据/环境等。研究源码的初始阶段可忽略该类目录。
  • XXX.go与XXXimpl.go是定义与实现的配套代码。
  • 同一事务分别存在与不同主题下。如protos目录下的peer与core目录下的peer都是peer相关的代码,但是相关主题的代码却分开放置。
  • no-tls标有no-tls的,说明相关代码未使用安全传输协议(TLS)。
  • util文件夹,一般都是该层级或该主题源码中具有辅助性,工具性的代码。

源码目录结构(v1.0)

  • bcssp 加密服务代码目录
  • common 全局公用代码目录
  • core 核心功能代码目录
  • docs 以.rst文件为核心,可编译生成文档。说明文档的目录
  • events 事件代码目录,用于生产和消费信息
  • examples 示例目录
  • gossip 本意是绯闻的意思,是一种可达到去中心化,有一定容错能力且可达到最终一致的传播算法
  • msp 会员服务代码目录
  • orderer 就理解成orderer目录就好,orderer也算是区域链中的专用名词,用于消息的订阅与分发处理
  • protos 原型目录,定义个各种原型和生成的对应的XXX.pb.go源码
  • vendor 原意是商贩,在此就是存放go中使用的全部的各种第三方包

GRPC

在fabric2.0+版本中,所有的grpc通过引入fabric-protos-go包来实现

在1.4.4版本中,则实现grpc服务的proto全在相关服务的protos目录下

https://blog.csdn.net/zhongdahong/article/details/104659228

运维监控metrics

实现监控数据解析(部分文件路径已经不适用于fabric2.0) https://zhuanlan.zhihu.com/p/165524328?utm_source=wechat_session

shim

shim是peer和chaincode之间的中间层。shim被移到了fabric-chaincode-go中

链码chaincode

定义链码接口

shim\interfaces.go

// Chaincode interface must be implemented by all chaincodes. The fabric runs
// the transactions by calling these functions as specified.
type Chaincode interface {
	// Init is called during Instantiate transaction after the chaincode container
	// has been established for the first time, allowing the chaincode to
	// initialize its internal data
	Init(stub ChaincodeStubInterface) pb.Response

	// Invoke is called to update or query the ledger in a proposal transaction.
	// Updated state variables are not committed to the ledger until the
	// transaction is committed.
	Invoke(stub ChaincodeStubInterface) pb.Response
}

返回的类型response

peer\proposal_response.pb.go

可以看到返回状态码需要和HTTP状态一致,可以自定义

// A response with a representation similar to an HTTP response that can
// be used within another message.
type Response struct {
	// A status code that should follow the HTTP status codes.
	Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"`
	// A message associated with the response code.
	Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	// A payload that can be used to include metadata with this response.
	Payload              []byte   `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

重点关注传入的参数类型ChaincodeStubInterface,shim\interfaces.go

主要用于为部署的链码提供访问和修改账本的方法。

type ChaincodeStubInterface interface {
	...
}

里面的方法有以下作用

  • 获取参数和函数名
  • 获取交易和通道ID
  • 调用其他链码(同通道的会更新读写集,不同通道的只有查询作用)
  • 增删改查键的状态(实际只是体现在读写集,验证后更新账本以及状态库)、读写键的验证参数
  • 范围查询(返回迭代器)、复合键查询、复合键创建和拆分(复合键在内部以0x00作为名称空间前缀)
  • 获取查询结果(仅支持couchdb)、获取键值的历史记录(这一行的方法只应该在只读交易中使用,因为不会在验证阶段验证)
  • 对于私有数据的相关操作,和键的操作类似的方法
  • 获得签名者、提案临时数据、绑定数据、装饰数据、签名提案、交易时间戳(涉及到的数据结构有SignatureHeader、ChaincodeProposalPayload、ChaincodeInput、SignedProposal、ChannelHeader)
  • 设置事件(涉及ChaincodeEvent、ChaincodeAction)

交易流程如下

ESCC和VSCC

在fabric1.2中。Escc和vscc被独立了出来(不能在core/scc目录下被找到了),提供了签名(验证策略)和state相关的依赖项,可以自己实现接口,编译成so文件引用。

但是可以参考core\endorser 以及core\committer,后续研究下

MSP

以上图片基于1.4版本仅供参考,源码使用2.0版本源码做分析

首先关注MSP的初始化

msp\mgmt\mgmt.go 

//进行msp的初始化
LoadLocalMspWithType(dir string, bccspConfig *factory.FactoryOpts, mspID, mspType string) error
{	
//获得配置信息msp.MSPConfig
conf<-msp.GetLocalMspConfigWithType(dir, bccspConfig, mspID, mspType)
//获得实现MSP接口的bccspmsp结构,setup是调用MSP接口的setup方法进行证书关联。
return GetLocalMSP(factory.GetDefault()).Setup(conf) 
}

其中的 GetLocalMSP会调用loadLocalMSP去new一个msp实例(此时用的bccsp,根据msp类型会有不同的操作,默认使用bccspMSP,返回msp.MSP)这里面有个factory包(通过GetDefault()获得了BCCSP),之后在讨论完什么是BCCSP后再研究。

这个MSP提供了什么功能方法?

接下来需要分别观察msp包和bccsp包

首先观察

hyperledger-fabric\msp\msp.go

hyperledger-fabric\msp\mspimpl.go

//IdentityDeserializer由MSPManger和MSP共同实现
type IdentityDeserializer interface	//身份证书反序列化接口
{
//反序列一个身份证明
DeserializeIdentity(serializedIdentity []byte) (Identity, error)
//检查证书格式
IsWellFormed(identity *msp.SerializedIdentity) error
}
//MSPManager是定义一个或多个MSP的管理器的接口。这实质上充当MSP调用的中介,并将MSP相关的调用路由到适当的MSP。
//此对象是不可变的,它只初始化一次就不会更改
type MSPManager interface {

//IdentityDeserializer接口需要由MSPManager实现
	IdentityDeserializer

	//根据配置信息设置MSP管理器实例
	Setup(msps []MSP) error

//GetMSPs提供会员服务提供商列表
	GetMSPs() (map[string]MSP, error)
}
type MSP interface {

	// IdentityDeserializer interface needs to be implemented by MSP
	IdentityDeserializer

	// Setup the MSP instance according to configuration information
	Setup(config *msp.MSPConfig) error

	// GetVersion returns the version of this MSP
	GetVersion() MSPVersion

	// GetType returns the provider type
	GetType() ProviderType

	// GetIdentifier returns the provider identifier
	GetIdentifier() (string, error)

	// GetSigningIdentity returns a signing identity corresponding to the provided identifier
	GetSigningIdentity(identifier *IdentityIdentifier) (SigningIdentity, error)

	// GetDefaultSigningIdentity returns the default signing identity
	GetDefaultSigningIdentity() (SigningIdentity, error)

	// GetTLSRootCerts returns the TLS root certificates for this MSP
	GetTLSRootCerts() [][]byte

	// GetTLSIntermediateCerts returns the TLS intermediate root certificates for this MSP
	GetTLSIntermediateCerts() [][]byte

//Validate检查提供的身份是否有效
	Validate(id Identity) error

	// SatisfiesPrincipal checks whether the identity matches
	// the description supplied in MSPPrincipal. The check may
	// involve a byte-by-byte comparison (if the principal is
	// a serialized identity) or may require MSP validation
	SatisfiesPrincipal(id Identity, principal *msp.MSPPrincipal) error
}

msp接口中主要注意以下四个方法IdentityDeserializer、Setup、Validate和SatisfiesPrincipal,其他get开头的方法都比较简单明了。

  • IdentityDeserializer身份反序列化
  • Setup证书关联
  • Validate身份有效性验证
  • SatisfiesPrincipal的作用是校验给定身份是否与principal中提供的描述匹配

MSP接口默认由bccspmsp实现,具体讲解见书P79

//hyperledger-fabric\msp\mspimpl.go
type bccspmsp struct {
	// version specifies the behaviour of this msp
	version MSPVersion
	// The following function pointers are used to change the behaviour
	// of this MSP depending on its version.
	// internalSetupFunc is the pointer to the setup function
	internalSetupFunc mspSetupFuncType

	// internalValidateIdentityOusFunc is the pointer to the function to validate identity's OUs
	internalValidateIdentityOusFunc validateIdentityOUsFuncType

	// internalSatisfiesPrincipalInternalFunc is the pointer to the function to check if principals are satisfied
	internalSatisfiesPrincipalInternalFunc satisfiesPrincipalInternalFuncType

	// internalSetupAdmin is the pointer to the function that setup the administrators of this msp
	internalSetupAdmin setupAdminInternalFuncType

	// list of CA certs we trust
	rootCerts []Identity

	// list of intermediate certs we trust
	intermediateCerts []Identity

	// list of CA TLS certs we trust
	tlsRootCerts [][]byte

	// list of intermediate TLS certs we trust
	tlsIntermediateCerts [][]byte

	// certificationTreeInternalNodesMap whose keys correspond to the raw material
	// (DER representation) of a certificate casted to a string, and whose values
	// are boolean. True means that the certificate is an internal node of the certification tree.
	// False means that the certificate corresponds to a leaf of the certification tree.
	certificationTreeInternalNodesMap map[string]bool

	// list of signing identities
	signer SigningIdentity

	// list of admin identities
	admins []Identity

	// the crypto provider
	bccsp bccsp.BCCSP

	// the provider identifier for this MSP
	name string

	// verification options for MSP members
	opts *x509.VerifyOptions

	// list of certificate revocation lists
	CRL []*pkix.CertificateList

	// list of OUs
	ouIdentifiers map[string][][]byte

	// cryptoConfig contains
	cryptoConfig *m.FabricCryptoConfig

	// NodeOUs configuration
	ouEnforcement bool
	// These are the OUIdentifiers of the clients, peers, admins and orderers.
	// They are used to tell apart these entities
	clientOU, peerOU, adminOU, ordererOU *OUIdentifier
}

可以看到,密码学相关的服务由bccsp提供,其中最重要的成员为BCCSP,封装了常用的加密标准和算法。

hyperledger-fabric\bccsp\bccsp.go

type BCCSP interface {

	// KeyGen generates a key using opts.
	KeyGen(opts KeyGenOpts) (k Key, err error)

	// KeyDeriv derives a key from k using opts.
	// The opts argument should be appropriate for the primitive used.
	KeyDeriv(k Key, opts KeyDerivOpts) (dk Key, err error)

	// KeyImport imports a key from its raw representation using opts.
	// The opts argument should be appropriate for the primitive used.
	KeyImport(raw interface{}, opts KeyImportOpts) (k Key, err error)

	// GetKey returns the key this CSP associates to
	// the Subject Key Identifier ski.
	GetKey(ski []byte) (k Key, err error)

	// Hash hashes messages msg using options opts.
	// If opts is nil, the default hash function will be used.
	Hash(msg []byte, opts HashOpts) (hash []byte, err error)

	// GetHash returns and instance of hash.Hash using options opts.
	// If opts is nil, the default hash function will be returned.
	GetHash(opts HashOpts) (h hash.Hash, err error)

	// Sign signs digest using key k.
	// The opts argument should be appropriate for the algorithm used.
	//
	// Note that when a signature of a hash of a larger message is needed,
	// the caller is responsible for hashing the larger message and passing
	// the hash (as digest).
	Sign(k Key, digest []byte, opts SignerOpts) (signature []byte, err error)

	// Verify verifies signature against key k and digest
	// The opts argument should be appropriate for the algorithm used.
	Verify(k Key, signature, digest []byte, opts SignerOpts) (valid bool, err error)

	// Encrypt encrypts plaintext using key k.
	// The opts argument should be appropriate for the algorithm used.
	Encrypt(k Key, plaintext []byte, opts EncrypterOpts) (ciphertext []byte, err error)

	// Decrypt decrypts ciphertext using key k.
	// The opts argument should be appropriate for the algorithm used.
	Decrypt(k Key, ciphertext []byte, opts DecrypterOpts) (plaintext []byte, err error)
}

基于BCCSP的msp实例化由newBccspMsp实现(hyperledger-fabric\msp\mspimpl.go)

func newBccspMsp(version MSPVersion, defaultBCCSP bccsp.BCCSP) (MSP, error)

newBccspMsp根据传入的MSP版本号不同,采用的证书关联、验证策略等会有所不同,最后返回一个MSP。

而MSP采用的BCCSP也是传入newBccspMsp的参数之一。

newBccspMsp会在几个地方被用到,其本质作用就是实例化一个msp并返回。

最典型的被用到的场景就是一开始讨论到的LoadLocalMspWithType。

调用逻辑:LoadLocalMspWithType->GetLocalMSP->loadLocalMSP->New->newBccspMsp
注意:其中的msp.New在hyperledger-fabric\msp\factory.go

另外还在NewBccspMspWithKeyStore(hyperledger-fabric\msp\mspimpl.go)会调用newBccspMsp

// NewBccspMspWithKeyStore allows to create a BCCSP-based MSP whose underlying
// crypto material is available through the passed keystore
func NewBccspMspWithKeyStore(version MSPVersion, keyStore bccsp.KeyStore, bccsp bccsp.BCCSP) (MSP, error) {
	thisMSP, err := newBccspMsp(version, bccsp)
	if err != nil {
		return nil, err
	}

	csp, err := sw.NewWithParams(
		factory.GetDefaultOpts().SW.Security,
		factory.GetDefaultOpts().SW.Hash,
		keyStore)
	if err != nil {
		return nil, err
	}
	thisMSP.(*bccspmsp).bccsp = csp

	return thisMSP, nil
}

查看调用这个实例化函数的地方,传入的bccsp参数都是通过下面这个语句生成的

//ks是传给NewBccspMspWithKeyStore的bccsp.KeyStore
ks, err := sw.NewFileBasedKeyStore(nil, filepath.Join(dir, "keystore"), true)
//cryptoProvider是要传给NewBccspMspWithKeyStore的bccsp.BCCSP
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())

到这里先不继续深究了,盲猜keystore是用于选择是否将密钥保存在硬盘中(不保存的话关机就丢失),dir参数即想要存储的文件路径。回去接着关注BCCSP

首先明确BCCSP的两种实现有SW和PKCS11。其中SW为基于软件的BCCSP实现,支持的加密算法比较多。而pkcs11借助了口令保护的数据库或者硬件设备。故主要关注SW即可。

在factory包中,定义了接口BCCSPFactory,它的两个实现方式为SW和PKCS11

hyperledger-fabric\bccsp\factory\factory.go
type BCCSPFactory interface {

	// Name returns the name of this factory,例如“SW”
	Name() string

	// Get returns an instance of BCCSP using opts.返回BCCSP实例
	Get(opts *FactoryOpts) (bccsp.BCCSP, error)
}

之前一开始提到的GetDefault(),也在这个包里。如果defaultBCCSP没有定义,则使用SW类型的bootBCCSP

hyperledger-fabric\bccsp\factory\factory.go
func GetDefault() bccsp.BCCSP {
	if defaultBCCSP == nil {
		logger.Debug("Before using BCCSP, please call InitFactories(). Falling back to bootBCCSP.")
		bootBCCSPInitOnce.Do(func() {
			var err error
			bootBCCSP, err = (&SWFactory{}).Get(GetDefaultOpts())
			if err != nil {
				panic("BCCSP Internal error, failed initialization with GetDefaultOpts!")
			}
		})
		return bootBCCSP
	}
	return defaultBCCSP
}

GetDefaultOpts()是用于返回BCCSP实例化时的默认配置(哈希家族、安全层数)

hyperledger-fabric\bccsp\factory\opts.go
func GetDefaultOpts() *FactoryOpts {
	return &FactoryOpts{
		Default: "SW",
		SW: &SwOpts{
			Hash:     "SHA2",
			Security: 256,
		},
	}
}

接下来主要关注SW的Get实现.

这个方法首先检查FileKeystore选项

  • 若不为空,则用文件去存储密钥,路径swOpts.FileKeystore.KeyStorePath
  • 若为空,则默认为临时存储密钥。

最后调用 sw.NewWithParams(swOpts.Security, swOpts.Hash, ks),返回根据配置实例化后的BCCSP

hyperledger-fabric\bccsp\factory\swfactory.go
// Get returns an instance of BCCSP using Opts.
func (f *SWFactory) Get(config *FactoryOpts) (bccsp.BCCSP, error) {
	// Validate arguments
	if config == nil || config.SW == nil {
		return nil, errors.New("Invalid config. It must not be nil.")
	}

	swOpts := config.SW

	var ks bccsp.KeyStore
	switch {
	case swOpts.FileKeystore != nil:
		fks, err := sw.NewFileBasedKeyStore(nil, swOpts.FileKeystore.KeyStorePath, false)
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to initialize software key store")
		}
		ks = fks
	default:
		// Default to ephemeral key store
		ks = sw.NewDummyKeyStore()
	}

	return sw.NewWithParams(swOpts.Security, swOpts.Hash, ks)
}

接下来还可以查看sw包(hyperledger-fabric\bccsp\sw)看看具体如何实例化swbccsp的,swbccsp内部的结构是怎么样的。具体看书P83

那么最后再看看证书关联(Setup)具体涉及到哪些结构和方法

此时bccspmsp已经构建好了,但只有框架和加密算法,还需要关联证书,从而提供成员身份服务提供者的完整功能。

hyperledger-fabric\msp\mspimpl.go
func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error {
	if conf1 == nil {
		return errors.New("Setup error: nil conf reference")
	}

	// given that it's an msp of type fabric, extract the MSPConfig instance
	conf := &m.FabricMSPConfig{}
	err := proto.Unmarshal(conf1.Config, conf)
	if err != nil {
		return errors.Wrap(err, "failed unmarshalling fabric msp config")
	}

	// set the name for this msp
	msp.name = conf.Name
	mspLogger.Debugf("Setting up MSP instance %s", msp.name)

	// setup
	return msp.internalSetupFunc(conf)
}

最后的internalSetupFunc是bccspmsp结构体中的字段,由newBccspMsp函数根据参数MSP版本来赋值。

	case MSPv1_4_3:
		theMsp.internalSetupFunc = theMsp.setupV142
		theMsp.internalValidateIdentityOusFunc = theMsp.validateIdentityOUsV142
		theMsp.internalSatisfiesPrincipalInternalFunc = theMsp.satisfiesPrincipalInternalV142
		theMsp.internalSetupAdmin = theMsp.setupAdminsV142

以MSPv1_4_3为例,观察setupV142

hyperledger-fabric\msp\mspimplsetup.go
func (msp *bccspmsp) setupV142(conf *m.FabricMSPConfig) error {
	err := msp.preSetupV142(conf)
	if err != nil {
		return err
	}

	err = msp.postSetupV142(conf)
	if err != nil {
		return err
	}

	return nil
}

涉及两个函数,前者preSetupV142根据配置设置bccspmsp结构成员(在设置过程中也会进行证书链的检查),后者postSetupV142对管理员证书进行合法性验证。具体见书P87

这两个函数中会用到许多方法,CA证书相关(建立根证书池和中间证书池(预初始化msp.opts),检查证书链是否唯一(getIdentityFromCon),检查证书签名是否在椭圆曲线阶群范围内),管理员证书列表(通过getIdentityFromCon来读取证书生成身份ID,存入msp.admins),撤销证书列表,签名身份列表,证书的签名和验证,序列化和反序列化等等。

序列化可以再深入了解下,首先需要关注下identity。

这个结构体如下所见,存有一些指针,指向实例的身份id,证书,公钥,msp等等

hyperledger-fabric\msp\identities.go
type identity struct {
	// id contains the identifier (MSPID and identity identifier) for this instance
	id *IdentityIdentifier

	// cert contains the x.509 certificate that signs the public key of this instance
	cert *x509.Certificate

	// this is the public key of this instance
	pk bccsp.Key

	// reference to the MSP that "owns" this identity
	msp *bccspmsp

	// validationMutex is used to synchronise memory operation
	// over validated and validationErr
	validationMutex sync.Mutex

	// validated is true when the validateIdentity function
	// has been called on this instance
	validated bool

	// validationErr contains the validation error for this
	// instance. It can be read if validated is true
	validationErr error
}

前面多次提到了getIdentityFromCon,它会调用newIdentity。

newIdentity有两个步骤,证书审查sanitizeCert,以及封装identity实例并返回。

那么接下来可以看看序列化操作了,它的接收器就是identity。大体步骤是先将证书编码,然后再加上Mspid,一起序列化(调用Marshal)。

hyperledger-fabric\msp\identities.go
func (id *identity) Serialize() ([]byte, error) {
	pb := &pem.Block{Bytes: id.cert.Raw, Type: "CERTIFICATE"}
	pemBytes := pem.EncodeToMemory(pb)
	if pemBytes == nil {
		return nil, errors.New("encoding of identity failed")
	}

	// We serialize identities by prepending the MSPID and appending the ASN.1 DER content of the cert
	sId := &msp.SerializedIdentity{Mspid: id.id.Mspid, IdBytes: pemBytes}
	idBytes, err := proto.Marshal(sId)
	if err != nil {
		return nil, errors.Wrapf(err, "could not marshal a SerializedIdentity structure for identity %s", id.id)
	}

	return idBytes, nil
}

相对应的,反序列化操作DeserializeIdentity大体步骤就是解码(调用Unmarshal),检查mspid,用得到的数据调用newIdentity。(反序列函数在hyperledger-fabric\msp\mspimpl.go)

现在,让我们回到最开头讲的MSP接口,我们还剩两个方法需要关注

  • Validate身份有效性验证
  • SatisfiesPrincipal的作用是校验给定身份是否与principal中提供的描述匹配

先看Validate

hyperledger-fabric\msp\mspimpl.go
func (msp *bccspmsp) Validate(id Identity) error {
	mspLogger.Debugf("MSP %s validating identity", msp.name)

	switch id := id.(type) {
	// If this identity is of this specific type,
	// this is how I can validate it given the
	// root of trust this MSP has
	case *identity:
		return msp.validateIdentity(id)
	default:
		return errors.New("identity type not recognized")
	}
}

只支持校验*identity,调用validateIdentity,大致步骤为

  1. 把id上锁,验证结束后解锁
  2. 获得id的证书链
  3. 检查证书是否被撤销
  4. 验证身份的组织单元信息是否合法

每个阶段的具体讲解见书P103

hyperledger-fabric\msp\mspimplvalidate.go
func (msp *bccspmsp) validateIdentity(id *identity) error {
	id.validationMutex.Lock()
	defer id.validationMutex.Unlock()

	// return cached validation value if already validated
	if id.validated {
		return id.validationErr
	}

	id.validated = true

	validationChain, err := msp.getCertificationChainForBCCSPIdentity(id)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not obtain certification chain")
		return id.validationErr
	}

	err = msp.validateIdentityAgainstChain(id, validationChain)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not validate identity against certification chain")
		return id.validationErr
	}

	err = msp.internalValidateIdentityOusFunc(id)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not validate identity's OUs")
		return id.validationErr
	}

	return nil
}

其中验证身份的组织单元信息这个函数基于msp版本而进行不同的赋值,最新版本的函数validateIdentityOUsV142会在旧版本的函数theMsp.validateIdentityOUsV1上多进行些操作

从下面的代码可以看出,validateIdentityOUsV1的步骤如下:

  1. 检查msp.ouIdentifiers长度是否大于0,
  2. 求msp.ouIdentifiers和传入id的组织单元列表的交集,比较的是组织单元相关的证书链的哈希
hyperledger-fabric\msp\mspimplvalidate.go
func (msp *bccspmsp) validateIdentityOUsV1(id *identity) error {
	// Check that the identity's OUs are compatible with those recognized by this MSP,
	// meaning that the intersection is not empty.
	if len(msp.ouIdentifiers) > 0 {
		found := false

		for _, OU := range id.GetOrganizationalUnits() {
			certificationIDs, exists := msp.ouIdentifiers[OU.OrganizationalUnitIdentifier]

			if exists {
				for _, certificationID := range certificationIDs {
					if bytes.Equal(certificationID, OU.CertifiersIdentifier) {
						found = true
						break
					}
				}
			}
		}

		if !found {
			if len(id.GetOrganizationalUnits()) == 0 {
				return errors.New("the identity certificate does not contain an Organizational Unit (OU)")
			}
			return errors.Errorf("none of the identity's organizational units %s are in MSP %s", OUIDs(id.GetOrganizationalUnits()), msp.name)
		}
	}

	return nil
}

在validateIdentityOUsV142中会在开头调用validateIdentityOUsV1,之后会进行一系列enforcement相关的操作。这个enforcement具体的值是否开启nodeOU。nodeOU在官方文档解释是一种特殊的OU用法。普通的OU是将组织划分为多个组织单元,类似部门的概念。而nodeOU则是会给组织单元中的身份赋予一定的角色,每次去向CA注册的时候就可以将角色作为参数。

再关注下SatisfiesPrincipal。具体见书P106。分为两步(Principal:确定权限的附加属性)

  1. 搜集规则collectPrincipals,实际是递归函数,支持组合策略
  2. internalSatisfiesPrincipalInternalFunc(id, principal),也是根据msp版本不同进行赋值的。检查给定的身份是否与所有搜集的规则匹配。根据规则类型的不同(角色、身份、组织单元等等)从而采取不同的检查方法。 最新版本的调用链是 satisfiesPrincipalInternalV142->satisfiesPrincipalInternalV13->satisfiesPrincipalInternalPreV13
hyperledger-fabric\msp\mspimpl.go
// SatisfiesPrincipal returns nil if the identity matches the principal or an error otherwise
func (msp *bccspmsp) SatisfiesPrincipal(id Identity, principal *m.MSPPrincipal) error {
	principals, err := collectPrincipals(principal, msp.GetVersion())
	if err != nil {
		return err
	}
	for _, principal := range principals {
		err = msp.internalSatisfiesPrincipalInternalFunc(id, principal)
		if err != nil {
			return err
		}
	}
	return nil
}

Principal相关的结构体如下,

PrincipalClassification描述princiapl的类型

Principal为承载类型的容器

fabric-proto-go\msp\msp_principal.pb.go
type MSPPrincipal struct {
	// Classification describes the way that one should process
	// Principal. An Classification value of "ByOrganizationUnit" reflects
	// that "Principal" contains the name of an organization this MSP
	// handles. A Classification value "ByIdentity" means that
	// "Principal" contains a specific identity. Default value
	// denotes that Principal contains one of the groups by
	// default supported by all MSPs ("admin" or "member").
	PrincipalClassification MSPPrincipal_Classification `protobuf:"varint,1,opt,name=principal_classification,json=principalClassification,proto3,enum=common.MSPPrincipal_Classification" json:"principal_classification,omitempty"`
	// Principal completes the policy principal definition. For the default
	// principal types, Principal can be either "Admin" or "Member".
	// For the ByOrganizationUnit/ByIdentity values of Classification,
	// PolicyPrincipal acquires its value from an organization unit or
	// identity, respectively.
	// For the Combined Classification type, the Principal is a marshalled
	// CombinedPrincipal.
	Principal            []byte   `protobuf:"bytes,2,opt,name=principal,proto3" json:"principal,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

类型有以下

type MSPPrincipal_Classification int32

const (
	MSPPrincipal_ROLE MSPPrincipal_Classification = 0
	// one of a member of MSP network, and the one of an
	// administrator of an MSP network
	MSPPrincipal_ORGANIZATION_UNIT MSPPrincipal_Classification = 1
	// groupping of entities, per MSP affiliation
	// E.g., this can well be represented by an MSP's
	// Organization unit
	MSPPrincipal_IDENTITY MSPPrincipal_Classification = 2
	// identity
	MSPPrincipal_ANONYMITY MSPPrincipal_Classification = 3
	// an identity to be anonymous or nominal.
	MSPPrincipal_COMBINED MSPPrincipal_Classification = 4
)

签名策略

签名策略通常包含两部分

  • 背书签名的主体Principal(MSPPrincipal的成员),即期望的来源。签名策略只支持member和admin两种
  • 门槛thsholdgate(字符表达式,表示策略需要达到的条件)
fabric-proto-go\common\policies.pb.go
type SignaturePolicy struct {
	// Types that are valid to be assigned to Type:
	//	*SignaturePolicy_SignedBy
	//	*SignaturePolicy_NOutOf_
	Type                 isSignaturePolicy_Type `protobuf_oneof:"Type"`
	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
	XXX_unrecognized     []byte                 `json:"-"`
	XXX_sizecache        int32                  `json:"-"`
}

实现isSignaturePolicy_Type接口的结构有以下两个

type SignaturePolicy_SignedBy struct {
	SignedBy int32 `protobuf:"varint,1,opt,name=signed_by,json=signedBy,proto3,oneof"`
}

type SignaturePolicy_NOutOf_ struct {
	NOutOf *SignaturePolicy_NOutOf `protobuf:"bytes,2,opt,name=n_out_of,json=nOutOf,proto3,oneof"`
}

第一个,它的成员SignedBy表示实体在MSPPrinciapl列表的下标,指示了Principal来源。

第二个,它的成员的结构如下

type SignaturePolicy_NOutOf struct {
	N                    int32              `protobuf:"varint,1,opt,name=n,proto3" json:"n,omitempty"`
	Rules                []*SignaturePolicy `protobuf:"bytes,2,rep,name=rules,proto3" json:"rules,omitempty"`
	XXX_NoUnkeyedLiteral struct{}           `json:"-"`
	XXX_unrecognized     []byte             `json:"-"`
	XXX_sizecache        int32              `json:"-"`
}
  • n表示需要多少个签名满足。
  • Rules是个递归的结构,指示主体。如果Rules的类型是SignaturePolicy_SignedBy,那么递归终止。

对签名策略进行封装的结构为

type SignaturePolicyEnvelope struct {
	Version              int32               `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
	Rule                 *SignaturePolicy    `protobuf:"bytes,2,opt,name=rule,proto3" json:"rule,omitempty"`
	Identities           []*msp.MSPPrincipal `protobuf:"bytes,3,rep,name=identities,proto3" json:"identities,omitempty"`
	XXX_NoUnkeyedLiteral struct{}            `json:"-"`
	XXX_unrecognized     []byte              `json:"-"`
	XXX_sizecache        int32               `json:"-"`
}

最后再看看策略的编译

common\cauthdsl\cauthdsl.go
func compile(policy *cb.SignaturePolicy, identities []*mb.MSPPrincipal) (func([]msp.Identity, []bool) bool, error) {
...
}

它会根据签名策略构建返回一个匿名的评估函数,可以用这个函数去评估签名数组SignedData(从背书消息中得到)是否符合预定义的签名策略。大致步骤为:

检查签名策略的类型:

  • 若为SignedBy,直接检查,检查完毕返回匿名函数;
  • 若为NOutOf_,则递归检查。

检查的步骤为:

  1. 检查主体是否在主体列表中,
  2. 判断是否检查过策略,检查过的策略不会重复检查,(因为是递归的,可能会有很多重复的策略,所以没必要重新检查)
  3. 反序列化身份,并调用SatisfiesPrincipal,检查主体身份与规则的描述是否匹配,
  4. 调用Verify验证签名是否合法,
  5. 检查通过后设置已检查标志。