Fabric应用开发者需要了解经典软件工程管理:
Fabric应用开发者需要精通PKI密码体系,包括:
chaincode开发支持的语言包括:
devOps运维开发技术栈包括:
chaincode的package包名必须是main,因为容器里会去找main函数。
必须实现Init和Invoke两个函数,这样才会构成一个chaincode。在初始化和升级chaincode时,会调用到Init函数;query以及invoke的第一阶段,会调用到Invoke函数。
package main
type StressChaincode struct{}
//called when initialize, upgrade
func (t *StressChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
//called when query, phase-1 of invoke
func (t *StressChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
func main() {
err:=shim.Start(new(StressChaincode))
}
从外部传入chaincode的参数列表可以通过下面的两个接口获取到。GetFunctionAndParameters可以将第一个元素转换成功能名称。
GetArgs() [][]byte
GetFunctionAndParameters() (string, []string) //returns 1st argument as function,"fcn", rest as params
Fabric的数据存储采用的是KV形式,根据key值就可以从数据库中获取、更新、删除数据。
GetState(key string)
PutState(key string, value []byte)
DelState(key string)
GetHistoryForKey可以返回关于某个key的所有历史操作。要求在peer节点配置“core.ledger.history.enableHistoryDatabase=true”,才能正常调用。
GetHistoryForKey(key string) iterator<KeyModification>
列表的每个元素都是结构体KeyModification。
type KeyModification struct {
TxId string // transaction交易号
Value []byte // 历史值
Timestamp TimeLong // timestamp provided by the client in the proposal header.
IsDelete bool // 是否是删除操作
}
根据键值返回对应区间的所有状态数据,限制最多100个,如果超过100个建议使用pageAPI,或者超过100个的时候不要继续迭代了,这时可以修改startKey再做一次GetStateByRange查询。
GetStateByRange(startKey, endKey string) iterator<KV>
列表的每个元素都是结构体KV。
type KV struct {
Namespace string // same as chaincode ID 当前运行的智能合约ID,其他API都不能获取该数据。
Key string
Value []byte
}
用不可打印(unprintable)的字符拼接成key,称为复合key。由于包含了不可打印字符,通过GetStateByRange将查询不到想要的数据。
复合key可以扩展数据命名空间,防止键重叠。比如两个系统可能有相同的合同号,通常用“公司名+下划线+合同号”作为key,但是下划线也可能是合同号一部分,会引发一些问题,通过composite key用不可打印字符连接,因为很难从键盘输入一个不可打印的字符,解决合同号冲突的问题。
CreateCompositeKey(objectType string, attributes []string) (string, error)
SplitCompositeKey(compositeKey string) (string, []string, error)
启用CouchDB的先决条件: 需要在peer中配置以下内容
'CORE_LEDGER_STATE_STATEDATABASE=CouchDB',
'CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=${container_name}:5984',
'CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=${user}',
'CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=${password}'
启用CouchDB可以支持富查询(Rich query),利用couchdb的语法按查询规则做值检索,查询值里面的某些字段是否符合要求,只返回符合要求的值。而levelDB需要查询出所有数据一一判断,效率非常慢。富查询API如下:
GetQueryResult(query string)
但是CouchDB也有一些弊端:
复合API关注的不仅仅是chaincode自身的一些内容,还需要关注一些其他问题,比如需要运维、架构的配合。
通过InvokeChaincode可以进行跨智能合约的调用,InvokeChaincode不会创建另一个新交易,具有上下文不变性(Context invariant),包括CID、transient Map、TxID、timestamp等。
InvokeChaincode(calledChaincode, args, channel) peer.Response
通过GetCreator接口获取client的信息,识别当前交易是由谁发起的(不是背书人)。
GetCreator() ([]byte, error)
Client Identity Chaincode Library是一个工具类,将GetCreator获取的信息解析为结构体clientIdentityImpl,得到具体的交易发起人信息。
// ClientIdentityImpl implements the ClientIdentity interface
type clientIdentityImpl struct {
mspID string
cert *x509.Certificate
attrs *attrmgr.Attributes
}
GetTransient返回“ChaincodeProposalPayload.Transient”字段,它是一个包含一些数据(比如加密数据)的map,可用于实现某种形式的application-level的机密性。'ChaincodeProposalPayload’字段内容不会出在交易中,也不会出现在账本中。比如有时可能希望传入密钥给chaincode签名,就可以使用Transient Map。
我们完全可以将所有argument参数都放到Transient Map中,用作隐私保护,而argument中的数据是可以被其他peer看到的,因为argument是会包含在上下文中,黑客可以轻易获取到。Transient Map只有当前执行智能合约的peer可以看到。
使用PrivateData需要一些先决条件:
原来背书策略只能细化到chaincode这一级,意味着如果需要更新背书策略,只能通过升级智能合约的方式。有了key级别的背书策略,不需要升级chaincoe,只需要对某一些特定的key做背书策略配置,意味着有可能某些智能合约版本确在不同组织间不一样,但是在涉及这些key的操作,就会要求它们变成一样,不然就会背书失败。这样就使得key的意义更加广泛,变成资产的代表,对这个资产所有合约,都需要Key level endorsement来保证合约的一致性,不像以前key只是一个数据项,背书策略只能保证合约本身的一致性,合约放宽,key收紧。
获取和设置背书策略的API如下:
GetStateValidationParameter(key string) ([]byte, error)
SetStateValidationParameter(key string, ep []byte) error // ep是通过KeyLevelEndorsement的build接口构造出来的,因为只能add或delet,所有只支持'AND'的关系
如果一个key的数据被修改,而且账本中存在key级别的背书策略,这个key级别的背书策略将会覆盖chaincode级别的背书策略。这意味着chaincode级别的背书策略不用一直修改,不用再管理了,只要SetStateValidationParameter就可以了。
Init函数需要注意以下几点:
Invoke是两阶段提交:
Query则只有第一个阶段。
由于Invoke的两阶段提交,chaincode中在putstate操作后,无法query到最新的结果,因为还没写到账本中,还需等待第二阶段提交。
Fabric节点运行模式分为一般模式和开发模式:
但是开发模式需要一定的外部配合,比如需要做一个开发模式的orderer,另外还不允许使用TLS。如果不想用开发模式,可以用MockStub代替stub,模拟chaincode的运行环境,进行测试;还可以用chaincode upgrade代替部署,这时不需要清除整个容器。
可以通过Binary可执行文件Cryptogen生成加密物料,另外也可以通过Fabric CA代替该功能。
创世区块文件是Orderer启动的必需文件。一般是通过Binary可执行文件configtxgen读取configtx.yaml配置文件,生成genesis.block。
channelName=testchainid
configtxgen -outputBlock $outputFile -profile $profile -channelID $channelName
Genesis block是系统channel的起始配置。
'ORDERER_GENERAL_LISTENADDRESS=0.0.0.0', // used to self identify
'ORDERER_GENERAL_TLS_ENABLED=${!!tls}',
'ORDERER_GENERAL_GENESISMETHOD=file',
'ORDERER_GENERAL_GENESISFILE=${exports.container.CONFIGTX}/${BLOCK_FILE}', -----
'ORDERER_GENERAL_LOCALMSPID=${id}', ----- // 与genesis block中的orderer的组织相对应
'ORDERER_GENERAL_LOCALMSPDIR=${configPath}', ----- // 与Cryptogen生成的文件路径相对应
与生成创世区块一样,同样是通过Binary可执行文件configtxgen来生成用户channel的引导文件。
configtxgen -outputCreateChannelTx $outputFile -profile $profile -channelID $channelName
mychannel.tx为用户channel的起始配置。
SDK读取channel引导文件mychannel.tx,传入到request域中,request中包含orderer参数,将channel的配置发送到orderer中,这样就完成了channel的创建。
创建于更新channel的方法是一样的。
Client.js#createChannel(request)
{
return this._createOrUpdateChannel
(request, request && request.envelope);
}
$ peer channel join
Channel.js#joinChannel
chaincode的生命周期包括Package、Install、Instantiate、Running、Upgrade等阶段,如下图:
安装chaincode实际上就是把chaincode打包一下,放到Peer上。
$ peer chaincode package -s -S -i "AND('OrgA.admin')" ... ccpack.out
$ peer chaincode signpackage ccpack.out signedccpack.out
打包时“ -s -S”选项可以创建可以由多个所有者签名的package,会被认为是还没被完全签名的输出。否则,该过程将创建一个仅包含实例化策略SignedCDS(仅有一个签名)。
实例化chaincode通过sendInstantiateProposal发出一个交易,然后进行交易共识。本质上还是调用channel._sendChaincodeProposal。
更新背书策略(endorsement policy)、更新私有数据收集策略( collection policy)、更新智能合约内容或希望重新执行Init函数时,都会需要执行chaincode升级操作。
升级chaincode通过sendUpgradeProposal发出一个交易,然后进行交易共识。 与实例化一样,本质上也是调用channel._sendChaincodeProposal。
[package ->] install -> upgrade(upgrade之前必须先install,以另一个chaincode version)
黑盒开发可以让开发者不用关心peer、orderer、channel这些模块,进行纯业务开发。
需要参与的点如下: