环境准备
- JDK 1.8+
- Apache Maven
- IDEA / eclipse
下载项目
git clone https://github.com/hyperledger/fabric-sdk-java
运行Fabric环境
cd fabric-sdk-java/src/test/fixture/sdkintegration
./fabric.sh up
运行结果
导入项目
由于选择的是eclipse,所以一直无法配置好依赖,使得一直没有能够成功导入项目,最后放弃选择使用IDEA,IDEA导入maven项目后,只需安装protobuf support,再进行complie即可,这里就不详述了。
运行测试Demo——End2endIT.java
运行结果分析
- 先构建通道foo,把org1加入该通道,运行该通道
- 初始化a为100,b为200,并把此交易送至orderer中
- a转账100给b,背书后,把交易送至orderer
- 查询b的值
- 后面就是创建调用另一个通道bar,进行相同的操作,这里也不详细罗列了
End2endIT源码分析(注释都在代码中)
- 先执行checkConfig()方法检查配置项
@Before
public void checkConfig() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, MalformedURLException, org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException {
out("\n\n\nRUNNING: %s.\n", testName);
resetConfig();//先重置配置文件org.hyperledger.fabric.sdk.helper.Config,config主要是fabric需要的配置项
configHelper.customizeConfig();//调用命令行输入的指定变量,覆盖掉上面的Config配置
testSampleOrgs = testConfig.getIntegrationTestsSampleOrgs();//获取testConfig配置的组织,TestConfig在End2endIT已经被new出来,里面也有大量的测试配置项
for (SampleOrg sampleOrg : testSampleOrgs) {//设置颁发证书的CA证书机构
String caName = sampleOrg.getCAName();
if (caName != null && !caName.isEmpty()) {
sampleOrg.setCAClient(HFCAClient.createNewInstance(caName, sampleOrg.getCALocation(), sampleOrg.getCAProperties()));
} else {
sampleOrg.setCAClient(HFCAClient.createNewInstance(sampleOrg.getCALocation(), sampleOrg.getCAProperties()));
}
}
}
- 执行setup()方法
@Test
public void setup() throws Exception {
if (sampleStoreFile.exists()) {
sampleStoreFile.delete();//模拟数据库存储使用了HFCSampletest.properties
}
sampleStore = new SampleStore(sampleStoreFile);//初始化存储
enrollUsersSetup(sampleStore);// 利用ca做初始化
runFabricTest(sampleStore);//核心方法
}
- 分析enrollUsersSetup()方法
for (SampleOrg sampleOrg : testSampleOrgs) {
HFCAClient ca = sampleOrg.getCAClient();//获得ca客户端
final String orgName = sampleOrg.getName();//组织名称
final String mspid = sampleOrg.getMSPID();//组织证书ID
ca.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());//设置CA客户端证书套件
if (testConfig.isRunningFabricTLS()) {
//从CA客户端获取TLS证书
final EnrollmentRequest enrollmentRequestTLS = new EnrollmentRequest();
enrollmentRequestTLS.addHost("localhost");
enrollmentRequestTLS.setProfile("tls");
final Enrollment enroll = ca.enroll("admin", "adminpw", enrollmentRequestTLS);
final String tlsCertPEM = enroll.getCert();
final String tlsKeyPEM = getPEMStringFromPrivateKey(enroll.getKey());
final Properties tlsProperties = new Properties();
tlsProperties.put("clientKeyBytes", tlsKeyPEM.getBytes(UTF_8));
tlsProperties.put("clientCertBytes", tlsCertPEM.getBytes(UTF_8));
clientTLSProperties.put(sampleOrg.getName(), tlsProperties);
sampleStore.storeClientPEMTLCertificate(sampleOrg, tlsCertPEM);
sampleStore.storeClientPEMTLSKey(sampleOrg, tlsKeyPEM);
}
//检查是否连接成功
HFCAInfo info = ca.info();
assertNotNull(info);
String infoName = info.getCAName();
if (infoName != null && !infoName.isEmpty()) {
assertEquals(ca.getCAName(), infoName);
}
//获取组织管理员(admin)
SampleUser admin = sampleStore.getMember(TEST_ADMIN_NAME, orgName);
if (!admin.isEnrolled()) {
//管理员只需在CA客户端进行登记
admin.setEnrollment(ca.enroll(admin.getName(), "adminpw"));
admin.setMspId(mspid);
}
//获取普通用户
SampleUser user = sampleStore.getMember(testUser1, sampleOrg.getName());
if (!user.isRegistered()) {
//如果未注册则生成注册请求
RegistrationRequest rr = new RegistrationRequest(user.getName(), "org1.department1");
//设置密码(注册请求发出去后,CA会返回一个密码)
user.setEnrollmentSecret(ca.register(rr, admin));
}
if (!user.isEnrolled()) {
//如果未登记则登记
user.setEnrollment(ca.enroll(user.getName(), user.getEnrollmentSecret()));
user.setMspId(mspid);
}
final String sampleOrgName = sampleOrg.getName();
final String sampleOrgDomainName = sampleOrg.getDomainName();//获取组织的命名域
//使用证书文件生成用户信息
SampleUser peerOrgAdmin = sampleStore.getMember(sampleOrgName + "Admin", sampleOrgName, sampleOrg.getMSPID(),
Util.findFileSk(Paths.get(testConfig.getTestChannelPath(), "crypto-config/peerOrganizations/",
sampleOrgDomainName, format("/users/Admin@%s/msp/keystore", sampleOrgDomainName)).toFile()),
Paths.get(testConfig.getTestChannelPath(), "crypto-config/peerOrganizations/", sampleOrgDomainName,
format("/users/Admin@%s/msp/signcerts/Admin@%s-cert.pem", sampleOrgDomainName, sampleOrgDomainName)).toFile());
sampleOrg.setPeerAdmin(peerOrgAdmin);//设置节点管理员(一个特殊的用户,可以创建通道,加入节点以及安装链码)
sampleOrg.addUser(user);//组织内添加用户
sampleOrg.setAdmin(admin); //设置管理员身份
}
- 分析 runFabricTest()方法
////////////////////////////
// Setup client
//Create instance of client.
HFClient client = HFClient.createNewInstance();//初始化一个客户端,类似cli
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());//设置加密算法
////////////////////////////
//Construct and run the channels
// 获取peerOrg1组织1
SampleOrg sampleOrg = testConfig.getIntegrationTestsSampleOrg("peerOrg1");
// 构建一个channel通道,Org1加入到该通道中
Channel fooChannel = constructChannel(FOO_CHANNEL_NAME, client, sampleOrg);
// 保存通道名称到数据库中(这里是存储到上面方法文件)
sampleStore.saveChannel(fooChannel);
// 安装链码、实例化链码、执行一个查询测试
runChannel(client, fooChannel, true, sampleOrg, 0);
assertFalse(fooChannel.isShutdown());
fooChannel.shutdown(true); // Force foo channel to shutdown clean up resources.
assertTrue(fooChannel.isShutdown());
assertNull(client.getChannel(FOO_CHANNEL_NAME));
out("\n");
// 下面是组织2的过程,跟组织1是类似的
sampleOrg = testConfig.getIntegrationTestsSampleOrg("peerOrg2");
Channel barChannel = constructChannel(BAR_CHANNEL_NAME, client, sampleOrg);
assertTrue(barChannel.isInitialized());
/**
* sampleStore.saveChannel uses {@link Channel#serializeChannel()}
*/
sampleStore.saveChannel(barChannel);
assertFalse(barChannel.isShutdown());
runChannel(client, barChannel, true, sampleOrg, 100); //run a newly constructed bar channel with different b value!
//let bar channel just shutdown so we have both scenarios.
out("\nTraverse the blocks for chain %s ", barChannel.getName());
// 对区块进行各种查询,包括区块读写集、区块数量高度等
blockWalker(client, barChannel);
assertFalse(barChannel.isShutdown());
assertTrue(barChannel.isInitialized());
out("That's all folks!");
- 分析constructChannel()方法(重点1)
a. 获取peer的admin用户,实际上是crytogen根据crypto-config.yaml配置的默认admin账户
SampleUser peerAdmin = sampleOrg.getPeerAdmin();
client.setUserContext(peerAdmin);
b. 接着是初始化orderer排序节点对象
Collection orderers = new LinkedList<>();
for (String orderName : sampleOrg.getOrdererNames()) {
Properties ordererProperties = testConfig.getOrdererProperties(orderName);
//example of setting keepAlive to avoid timeouts on inactive http2 connections.
// Under 5 minutes would require changes to server side to accept faster ping rates.
ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[] {5L, TimeUnit.MINUTES});
ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[] {8L, TimeUnit.SECONDS});
ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveWithoutCalls", new Object[] {true});
orderers.add(client.newOrderer(orderName, sampleOrg.getOrdererLocation(orderName),
ordererProperties));
}
c. 创建一个channel,这里使用的是Configtxgen 生成的“通道配置”文件
//Just pick the first orderer in the list to create the channel.
Orderer anOrderer = orderers.iterator().next();//选择第一个排序节点进行创建通道
orderers.remove(anOrderer); //从排序节点集合中移除已经在使用的节点
//通过读取channel.tx文件,生成信道配置对象
ChannelConfiguration channelConfiguration = new ChannelConfiguration(new File(TEST_FIXTURES_PATH + "/sdkintegration/e2e-2Orgs/channel/" + name + ".tx"));
//创建信道对象,只有一个签名,就是组织节点管理员。如果创建信道策略需要更多的签名,那么他们必须添加
Channel newChannel = client.newChannel(name, anOrderer, channelConfiguration, client.getChannelConfigurationSignature(channelConfiguration, sampleOrg.getPeerAdmin()));
out("Created channel %s", name);
d. client.newPeer创建peer节点,然后joinPeer加入通道channel中
boolean everyother = true; //test with both cases when doing peer eventing.
for (String peerName : sampleOrg.getPeerNames()) {
String peerLocation = sampleOrg.getPeerLocation(peerName);//获取节点位置
Properties peerProperties = testConfig.getPeerProperties(peerName); //获取节点属性
if (peerProperties == null) {
peerProperties = new Properties();
}
//Example of setting specific options on grpc's NettyChannelBuilder
peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
Peer peer = client.newPeer(peerName, peerLocation, peerProperties);//通过客户端生成节点对象
if (testConfig.isFabricVersionAtOrAfter("1.3")) {
//加入生成的节点
newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(EnumSet.of(PeerRole.ENDORSING_PEER, PeerRole.LEDGER_QUERY, PeerRole.CHAINCODE_QUERY, PeerRole.EVENT_SOURCE))); //Default is all roles.
} else {
if (doPeerEventing && everyother) {
newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(EnumSet.of(PeerRole.ENDORSING_PEER, PeerRole.LEDGER_QUERY, PeerRole.CHAINCODE_QUERY, PeerRole.EVENT_SOURCE))); //Default is all roles.
} else {
// Set peer to not be all roles but eventing.
newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(EnumSet.of(PeerRole.ENDORSING_PEER, PeerRole.LEDGER_QUERY, PeerRole.CHAINCODE_QUERY)));
}
}
out("Peer %s joined channel %s", peerName, name);
everyother = !everyother;
}
e. 给channel设置监听事件的grpc接口,最后进行初始化
//获取组织内事件记录节点集合
for (String eventHubName : sampleOrg.getEventHubNames()) {
//获取事件记录属性
final Properties eventHubProperties = testConfig.getEventHubProperties(eventHubName);
eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[] {5L, TimeUnit.MINUTES});
eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[] {8L, TimeUnit.SECONDS});
EventHub eventHub = client.newEventHub(eventHubName, sampleOrg.getEventHubLocation(eventHubName),
eventHubProperties);
newChannel.addEventHub(eventHub);
}
//信道实例化
newChannel.initialize();
out("Finished initialization channel %s", name);
- 分析runChannel()方法(重点2)
a. 安装链码
if (installChaincode) {
//一大段设置安装的链码信息
// .......
//发送安装链码的交易提案
responses = client.sendInstallProposal(installProposalRequest, peers);
//检查返回的结果,并输出相应的信息
//.......
b. 实例化链码,设置背书策略
//实例化链码
InstantiateProposalRequest instantiateProposalRequest = client.newInstantiationProposalRequest();
instantiateProposalRequest.setProposalWaitTime(DEPLOYWAITTIME);
instantiateProposalRequest.setChaincodeID(chaincodeID);
instantiateProposalRequest.setChaincodeLanguage(CHAIN_CODE_LANG);
instantiateProposalRequest.setFcn("init");
instantiateProposalRequest.setArgs(new String[] {"a", "500", "b", "" + (200 + delta)});
Map tm = new HashMap<>();
tm.put("HyperLedgerFabric", "InstantiateProposalRequest:JavaSDK".getBytes(UTF_8));
tm.put("method", "InstantiateProposalRequest".getBytes(UTF_8));
instantiateProposalRequest.setTransientMap(tm);
/*
policy OR(Org1MSP.member, Org2MSP.member) meaning 1 signature from someone in either Org1 or Org2
See README.md Chaincode endorsement policies section for more details.
*/
//设置背书策略
ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
chaincodeEndorsementPolicy.fromYamlFile(new File(TEST_FIXTURES_PATH + "/sdkintegration/chaincodeendorsementpolicy.yaml"));
instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);
out("Sending instantiateProposalRequest to all peers with arguments: a and b set to 100 and %s respectively", "" + (200 + delta));
successful.clear();
failed.clear();
//检查结果,并输出相应信息
c. 转账,查询,异常处理
channel.sendTransaction(successful, createTransactionOptions()
.userContext(client.getUserContext())
.shuffleOrders(false)
.orderers(channel.getOrderers())
.nOfEvents(nOfEvents)
).thenApply(transactionEvent -> {
//.......
// 调用example_cc.go里面的move方法,a给b转账
}.thenApply(transactionEvent -> {
//........
// query查询b的余额
try {
}).exceptionally(e -> {
//........
// 异常处理
}
d. 查询区块高度,信息之类的
总结
因时间有限,还并没有开始搭建自己的调用逻辑,之后会开始尝试。