fabric-smart-node(fsn)是基于faric的,通过虚拟orderer以及peer来使整体轻量化的fabric套件。目前这套组件均基于golang开发,java想要调用目前只能通过go开发的api---fabric-smart-client。
由此,java需要直接请求fsn,需要基于fsn提供的proto文件调用。这在技术上通过grpc调用本没有难度,但是因为fabric的调用涉及复杂的证书与签名,所以此文记录一下,避免重复踩坑。
涉及本次调用的proto如下:
service.proto
syntax = "proto3";
option go_package = "protos";
option cc_generic_services = true;
option java_package = "com.xxx.proto.java";
option java_generic_services = true;
option java_multiple_files = true;
package protos;
import "commands.proto";
// ViewService provides support to view management
service ViewService {
// ProcessCommand processes the passed command ensuring proper access control.
// The returned response allows the client to understand if the
// operation was successfully executed and if not, the response
// reports the reason of the failure.
rpc ProcessCommand(SignedCommand) returns (SignedCommandResponse);
rpc StreamCommand(SignedCommand) returns (stream SignedCommandResponse){};
}
2.command.proto
syntax = "proto3";
option go_package = "protos";
option cc_generic_services = true;
option java_package = "com.xxx.proto.java";
option java_generic_services = true;
option java_multiple_files = true;
package protos;
import "google/protobuf/timestamp.proto";
import "finality.proto";
// InitiateView is used to initiate a view
message InitiateView {
string fid = 1;
bytes input = 2;
}
message InitiateViewResponse {
string cid = 1;
}
// InitiateView is used to initiate a view
message CallView {
string fid = 1;
bytes input = 2;
}
message CallViewResponse {
bytes result = 1;
}
message TrackView {
string cid = 1;
}
message TrackViewResponse {
bytes payload = 1;
}
// Header is a generic replay prevention and identity message to include in a signed command
message Header {
// Timestamp is the local time when the message was created
// by the sender
google.protobuf.Timestamp timestamp = 1;
// Nonce is a sufficiently long random value
// used to ensure the request has enough entropy.
bytes nonce = 3;
// Creator of the message.
bytes creator = 4;
// TlsCertHash represents the hash of the client's TLS certificate
// when mutual TLS is enabled
bytes tls_cert_hash = 5;
}
// Command describes the type of operation that a client is requesting.
message Command {
// Header is the header of this command
Header header = 1;
// Payload is the payload of this command. It can assume one of the following value
oneof payload {
InitiateView initiateView = 2;
TrackView trackView = 3;
CallView callView = 4;
IsTxFinal isTxFinal = 5;
}
}
// SignedCommand is a command that carries the signature of the command's creator.
message SignedCommand {
// Command is the serialised version of a Command message
bytes command = 1;
// Signature is the signature over command
bytes signature = 2;
}
message CommandResponseHeader {
// Timestamp is the time that the message
// was created as defined by the sender
google.protobuf.Timestamp timestamp = 1;
// CommandHash is the hash computed on the concatenation of the SignedCommand's command and signature fields.
// If not specified differently, SHA256 is used
// The hash is used to link a response with its request, both for bookeeping purposes on an
// asynchronous system and for security reasons (accountability, non-repudiation)
bytes command_hash = 2;
// Creator is the identity of the party creating this message
bytes creator = 3;
}
// Error reports an application error
message Error {
// Message associated with this response.
string message = 1;
// Payload that can be used to include metadata with this response.
bytes payload = 2;
}
// A CommnandResponse is returned from a server to the command submitter.
message CommandResponse {
// Header of the response.
CommandResponseHeader header = 1;
// Payload of the response.
oneof payload {
Error err = 2;
InitiateViewResponse initiateViewResponse = 3;
TrackViewResponse trackViewResponse = 4;
CallViewResponse callViewResponse = 5;
IsTxFinalResponse isTxFinalResponse = 6;
}
}
// SignedCommandResponse is a signed command response
message SignedCommandResponse {
// Response is the serialised version of a CommandResponse message
bytes response = 1;
// Signature is the signature over command
bytes signature = 2;
}
3.finality.proto
syntax = "proto3";
option go_package = "protos";
option cc_generic_services = true;
option java_package = "com.xxx.proto.java";
option java_generic_services = true;
option java_multiple_files = true;
package protos;
message IsTxFinal {
string network = 1;
string channel = 2;
string txid = 3;
}
message IsTxFinalResponse {
bytes payload = 1;
}
根据以上proto文件可生成一下java文件
在这里我用
rpc ProcessCommand(SignedCommand) returns (SignedCommandResponse) 来举个例子
代码如下:
package com.xxx.proto.java;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import com.rongzer.rbaas.platform.server.fabric.sdkintegration.SampleUser;
import io.grpc.ManagedChannel;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.identity.SigningIdentity;
import org.hyperledger.fabric.sdk.identity.X509Enrollment;
import org.hyperledger.fabric.sdk.identity.X509SigningIdentity;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import javax.net.ssl.SSLException;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.util.UUID;
public class Test {
public static void main(String[] args) throws InterruptedException, IOException, IllegalAccessException, InvocationTargetException, InvalidArgumentException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException {
//该文件是approver的cert.pem证书文件,需要自己在已部署的fsn服务中找到并且换掉
String identityPath = "/fsc/crypto/peerOrganizations/fsc.example.com/peers/approver.fsc.example.com/msp/signcerts/approver.fsc.example.com-cert.pem";
//该文件是approver的私钥文件,需要自己在已部署的fsn服务中找到并且换掉
String privPath = "/fsc/crypto/peerOrganizations/fsc.example.com/peers/approver.fsc.example.com/msp/keystore/priv_sk";
//该文件是approver的ca文件,需要自己在已部署的fsn服务中找到并且换掉
String caPath = "/fsc/crypto/peerOrganizations/fsc.example.com/peers/approver.fsc.example.com/tls/ca.crt";
File ca = new File(caPath);
//File ca = new File(identityPath);
String fid = "funtionName";//需要调用的方法,由fsn服务端提供
JSONObject input = new JSONObject();
input.put("param","paramValue");//需要调用的方法所需的参数
CallView callView = CallView.newBuilder()
.setFid(fid)
.setInput(ByteString.copyFrom(input.toJSONString().getBytes(StandardCharsets.UTF_8)))
.build();
Header header = Header.newBuilder()
.setTimestamp(Timestamp.getDefaultInstance())
.setNonce(ByteString.copyFrom(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)))
.setCreator(ByteString.readFrom(new FileInputStream(identityPath)))
.setTlsCertHash(com.google.protobuf.ByteString.EMPTY)
.build();
Command command = Command.newBuilder()
.setCallView(callView)
.setHeader(header)
.build();
StringBuilder keyStr = new StringBuilder();
try (BufferedReader br = Files.newBufferedReader(Paths.get(privPath))){
String line ;
while ((line = br.readLine()) != null) {
keyStr.append(line).append("\n");;
}
}catch (Exception e){
throw e;
}
PrivateKey privateKey;
String keyString = keyStr.toString();
try (PEMParser parser = new PEMParser(new StringReader(keyString))) {
Object key = parser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
privateKey = converter.getPrivateKey((PrivateKeyInfo) key);
}catch (Exception e){
throw e;
}
SignedCommand signedCommand = SignedCommand.newBuilder()
.setCommand(command.toByteString())
.setSignature(ByteString.copyFrom(CryptoSuite.Factory.getCryptoSuite().sign(privateKey,command.toByteArray())))
.build();
String target = "ip:port";
SslContextBuilder builder = GrpcSslContexts.forClient();
SslContext sslContext = builder.trustManager(ca).build();
ManagedChannel channel = NettyChannelBuilder.forTarget(target)
//.usePlaintext()
.overrideAuthority("approver.fsc.example.com")//这里需要填写approver的server.crt中可使用者的DNS NAME
.negotiationType(NegotiationType.TLS)
.sslContext(sslContext)
.build();
ViewServiceGrpc.ViewServiceBlockingStub stub = ViewServiceGrpc.newBlockingStub(channel);
SignedCommandResponse res = stub.processCommand(signedCommand);
String cr = CommandResponse.parseFrom(res.getResponse()).getCallViewResponse().getResult().toStringUtf8();
System.out.println(cr);
}
private static SslContext buildSslContext(String trustCertCollectionFilePath,
String clientCertChainFilePath,
String clientPrivateKeyFilePath) throws SSLException {
SslContextBuilder builder = GrpcSslContexts.forClient();
if (trustCertCollectionFilePath != null) {
builder.trustManager(new File(trustCertCollectionFilePath));
}
if (clientCertChainFilePath != null && clientPrivateKeyFilePath != null) {
builder.keyManager(new File(clientCertChainFilePath), new File(clientPrivateKeyFilePath));
}
return builder.build();
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)