Skip to content

Commit

Permalink
FAB-8805 JSDK Service Discovery
Browse files Browse the repository at this point in the history
PS
13 Cleanup simplify default add orderer peer to properties
14 Provide options on getting endorsements.
15 Have discover/gossip finding peer in same org.
16 Need gossip leader set to false for v1.0.0
17 Move remap to discovery for better consistency in logs.
18 Provides alternative attempts on layouts on failures
19 Minor fix
20 Throw exception if endorsements not meet or allow inspection.
21 Method rename.
22 Fix on retry if empty
23 Run with non tls
24 Pair not everywhere.
25 Better recovery with bad service discovery peers. Minor cleanup
26 Proactive network discovery on non transaction event blocks.
27 byte[] does not do well in Sets.
   Bonus return discovered chaincode names and minor fix to discovery.
28 Detected upgrade chaincode event to do network discovery.
29 Restrict upgrade trigger to lscc chaincode id.
30 Mostly addressed Yacov's concerns.
31 Just changed generated protobuf package imports.

Change-Id: I52e3713f555697d308434a4c7950a72cf2bb677f
Signed-off-by: rickr <cr22rc@gmail.com>
  • Loading branch information
cr22rc committed Jul 10, 2018
1 parent a21df07 commit c5c31b8
Show file tree
Hide file tree
Showing 32 changed files with 2,524 additions and 123 deletions.
1,007 changes: 948 additions & 59 deletions src/main/java/org/hyperledger/fabric/sdk/Channel.java

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/main/java/org/hyperledger/fabric/sdk/EndorsementSelector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
*
* Copyright 2016,2018 DTCC, IBM - All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.hyperledger.fabric.sdk;

public interface EndorsementSelector {
ServiceDiscovery.SDEndorserState endorserSelector(ServiceDiscovery.SDChaindcode sdChaindcode);

EndorsementSelector ENDORSEMENT_SELECTION_RANDOM = ServiceDiscovery.ENDORSEMENT_SELECTION_RANDOM;
EndorsementSelector ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT = ServiceDiscovery.ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT;
}
15 changes: 12 additions & 3 deletions src/main/java/org/hyperledger/fabric/sdk/EndorserClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import io.grpc.ManagedChannelBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.protos.discovery.DiscoveryGrpc;
import org.hyperledger.fabric.protos.discovery.Protocol;
import org.hyperledger.fabric.protos.peer.EndorserGrpc;
import org.hyperledger.fabric.protos.peer.FabricProposal;
import org.hyperledger.fabric.protos.peer.FabricProposalResponse;
Expand All @@ -34,8 +36,8 @@ class EndorserClient {
private static final Log logger = LogFactory.getLog(EndorserClient.class);

private ManagedChannel managedChannel;
private EndorserGrpc.EndorserBlockingStub blockingStub;
private EndorserGrpc.EndorserFutureStub futureStub;
DiscoveryGrpc.DiscoveryFutureStub discoveryFutureStub;
private boolean shutdown = false;

/**
Expand All @@ -45,8 +47,8 @@ class EndorserClient {
*/
EndorserClient(ManagedChannelBuilder<?> channelBuilder) {
managedChannel = channelBuilder.build();
blockingStub = EndorserGrpc.newBlockingStub(managedChannel);
futureStub = EndorserGrpc.newFutureStub(managedChannel);
discoveryFutureStub = DiscoveryGrpc.newFutureStub(managedChannel);
}

synchronized void shutdown(boolean force) {
Expand All @@ -57,7 +59,8 @@ synchronized void shutdown(boolean force) {
ManagedChannel lchannel = managedChannel;
// let all referenced resource finalize
managedChannel = null;
blockingStub = null;
discoveryFutureStub = null;

futureStub = null;

if (lchannel == null) {
Expand Down Expand Up @@ -86,6 +89,12 @@ public ListenableFuture<FabricProposalResponse.ProposalResponse> sendProposalAsy
return futureStub.processProposal(proposal);
}

public ListenableFuture<Protocol.Response> sendDiscoveryRequestAsync(Protocol.SignedRequest signedRequest) throws PeerException {
if (shutdown) {
throw new PeerException("Shutdown");
}
return discoveryFutureStub.discover(signedRequest);
}

boolean isChannelActive() {
ManagedChannel lchannel = managedChannel;
Expand Down
95 changes: 93 additions & 2 deletions src/main/java/org/hyperledger/fabric/sdk/Endpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.AbstractMap;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -91,6 +92,13 @@ class Endpoint {
this.port = Integer.parseInt(purl.getProperty("port"));

if (properties != null) {

final AbstractMap.SimpleImmutableEntry<PrivateKey, X509Certificate[]> clientTLSProps = getClientTLSProps(properties);
if (clientTLSProps != null) {
clientCert = clientTLSProps.getValue();
clientKey = clientTLSProps.getKey();
}

if ("grpcs".equals(protocol)) {
CryptoPrimitives cp;
try {
Expand Down Expand Up @@ -123,6 +131,7 @@ class Endpoint {

}
pemBytes = bis.toByteArray();
logger.trace(format("Endpoint %s pemBytes: %s", url, Hex.encodeHexString(pemBytes)));

if (pemBytes.length == 0) {
pemBytes = null;
Expand Down Expand Up @@ -165,7 +174,9 @@ class Endpoint {
} else if (properties.containsKey("clientKeyFile") || properties.containsKey("clientCertFile")) {
if ((properties.getProperty("clientKeyFile") != null) && (properties.getProperty("clientCertFile") != null)) {
try {
logger.trace(format("Endpoint %s reading clientKeyFile: %s", url, properties.getProperty("clientKeyFile")));
ckb = Files.readAllBytes(Paths.get(properties.getProperty("clientKeyFile")));
logger.trace(format("Endpoint %s reading clientCertFile: %s", url, properties.getProperty("clientCertFile")));
ccb = Files.readAllBytes(Paths.get(properties.getProperty("clientCertFile")));
} catch (IOException e) {
throw new RuntimeException("Failed to parse TLS client key and/or cert", e);
Expand All @@ -183,17 +194,22 @@ class Endpoint {

if ((ckb != null) && (ccb != null)) {
String what = "private key";
byte[] whatBytes = new byte[0];
try {
logger.trace("client TLS private key bytes size:" + ckb.length);
whatBytes = ckb;
logger.trace("client TLS key bytes:" + Hex.encodeHexString(ckb));
clientKey = cp.bytesToPrivateKey(ckb);
logger.trace("converted TLS key.");
what = "certificate";
whatBytes = ccb;
logger.trace("client TLS certificate bytes:" + Hex.encodeHexString(ccb));
clientCert = new X509Certificate[] {(X509Certificate) cp.bytesToCertificate(ccb)};
logger.trace("converted client TLS certificate.");
tlsClientCertificatePEMBytes = ccb; // Save this away it's the exact pem we used.
} catch (CryptoException e) {
throw new RuntimeException("Failed to parse TLS client " + what, e);
logger.error(format("Failed endpoint %s to parse %s TLS client %s", url, what, new String(whatBytes)));
throw new RuntimeException(format("Failed endpoint %s to parse TLS client %s", url, what), e);
}
}

Expand Down Expand Up @@ -238,6 +254,8 @@ class Endpoint {
SslContextBuilder clientContextBuilder = getSslContextBuilder(clientCert, clientKey, sslprovider);
SslContext sslContext;

logger.trace(format("Endpoint %s final server pemBytes: %s", url, Hex.encodeHexString(pemBytes)));

try (InputStream myInputStream = new ByteArrayInputStream(pemBytes)) {
sslContext = clientContextBuilder
.trustManager(myInputStream)
Expand All @@ -250,20 +268,23 @@ class Endpoint {
.negotiationType(ntype);

if (cn != null) {
logger.debug(format("Endpoint %s, using CN overrideAuthority: '%s'", url, cn));
channelBuilder.overrideAuthority(cn);
}
addNettyBuilderProps(channelBuilder, properties);
} catch (SSLException sslex) {

throw new RuntimeException(sslex);
}
}
} else {
throw new RuntimeException("invalid protocol: " + protocol);
}
} catch (RuntimeException e) {
logger.error(e);
logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e);
throw e;
} catch (Exception e) {
logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e);
logger.error(e);
throw new RuntimeException(e);
}
Expand All @@ -273,6 +294,8 @@ SslContextBuilder getSslContextBuilder(X509Certificate[] clientCert, PrivateKey
SslContextBuilder clientContextBuilder = GrpcSslContexts.configure(SslContextBuilder.forClient(), sslprovider);
if (clientKey != null && clientCert != null) {
clientContextBuilder = clientContextBuilder.keyManager(clientKey, clientCert);
} else {
logger.debug(format("Endpoint %s with no ssl context", url));
}
return clientContextBuilder;
}
Expand Down Expand Up @@ -385,6 +408,68 @@ private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties

}

AbstractMap.SimpleImmutableEntry<PrivateKey, X509Certificate[]> getClientTLSProps(Properties properties) {

// check for mutual TLS - both clientKey and clientCert must be present
byte[] ckb = null, ccb = null;
if (properties.containsKey("clientKeyFile") && properties.containsKey("clientKeyBytes")) {
throw new RuntimeException("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set");
} else if (properties.containsKey("clientCertFile") && properties.containsKey("clientCertBytes")) {
throw new RuntimeException("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set");
} else if (properties.containsKey("clientKeyFile") || properties.containsKey("clientCertFile")) {
if ((properties.getProperty("clientKeyFile") != null) && (properties.getProperty("clientCertFile") != null)) {
try {
logger.trace(format("Endpoint %s reading clientKeyFile: %s", url, properties.getProperty("clientKeyFile")));
ckb = Files.readAllBytes(Paths.get(properties.getProperty("clientKeyFile")));
logger.trace(format("Endpoint %s reading clientCertFile: %s", url, properties.getProperty("clientCertFile")));
ccb = Files.readAllBytes(Paths.get(properties.getProperty("clientCertFile")));
} catch (IOException e) {
throw new RuntimeException("Failed to parse TLS client key and/or cert", e);
}
} else {
throw new RuntimeException("Properties \"clientKeyFile\" and \"clientCertFile\" must both be set or both be null");
}
} else if (properties.containsKey("clientKeyBytes") || properties.containsKey("clientCertBytes")) {
ckb = (byte[]) properties.get("clientKeyBytes");
ccb = (byte[]) properties.get("clientCertBytes");
if ((ckb == null) || (ccb == null)) {
throw new RuntimeException("Properties \"clientKeyBytes\" and \"clientCertBytes\" must both be set or both be null");
}
}

if ((ckb != null) && (ccb != null)) {
String what = "private key";
byte[] whatBytes = new byte[0];
try {

CryptoPrimitives cp;
try {
cp = new CryptoPrimitives();
} catch (Exception e) {
throw new RuntimeException(e);
}

logger.trace("client TLS private key bytes size:" + ckb.length);
whatBytes = ckb;
logger.trace("client TLS key bytes:" + Hex.encodeHexString(ckb));
PrivateKey clientKey = cp.bytesToPrivateKey(ckb);
logger.trace("converted TLS key.");
what = "certificate";
whatBytes = ccb;
logger.trace("client TLS certificate bytes:" + Hex.encodeHexString(ccb));
X509Certificate[] clientCert = new X509Certificate[] {(X509Certificate) cp.bytesToCertificate(ccb)};
logger.trace("converted client TLS certificate.");
tlsClientCertificatePEMBytes = ccb; // Save this away it's the exact pem we used.

return new AbstractMap.SimpleImmutableEntry<>(clientKey, clientCert);
} catch (CryptoException e) {
logger.error(format("Failed endpoint %s to parse %s TLS client %s", url, what, new String(whatBytes)));
throw new RuntimeException(format("Failed endpoint %s to parse TLS client %s", url, what), e);
}
}
return null;
}

ManagedChannelBuilder<?> getChannelBuilder() {
return this.channelBuilder;
}
Expand All @@ -397,4 +482,10 @@ int getPort() {
return this.port;
}

static Endpoint createEndpoint(String url, Properties properties) {

return new Endpoint(url, properties);

}

}
2 changes: 1 addition & 1 deletion src/main/java/org/hyperledger/fabric/sdk/EventHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ synchronized boolean connect(final TransactionContext transactionContext, final

lastConnectedAttempt = System.currentTimeMillis();

Endpoint endpoint = new Endpoint(url, properties);
Endpoint endpoint = Endpoint.createEndpoint(url, properties);
managedChannel = endpoint.getChannelBuilder().build();

clientTLSCertificateDigest = endpoint.getClientTLSCertificateDigest();
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/hyperledger/fabric/sdk/HFClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ public Channel deSerializeChannel(byte[] channelBytes)
* Supported properties
* <ul>
* <li>pemFile - File location for x509 pem certificate for SSL.</li>
* <li>pemBytes - byte array for x509 pem certificates for SSL</li>
* <li>trustServerCertificate - boolen(true/false) override CN to match pemFile certificate -- for development only.
* If the pemFile has the target server's certificate (instead of a CA Root certificate),
* instruct the TLS client to trust the CN value of the certificate in the pemFile,
Expand Down Expand Up @@ -414,12 +415,13 @@ public User setUserContext(User userContext) throws InvalidArgumentException {
/**
* Create a new Eventhub.
*
* @param name name of Orderer.
* @param name name of Eventhub.
* @param grpcURL url location of orderer grpc or grpcs protocol.
* @param properties <p>
* Supported properties
* <ul>
* <li>pemFile - File location for x509 pem certificate for SSL.</li>
* <li>pemBytes - byte array for x509 pem certificates for SSL</li>
* <li>trustServerCertificate - boolean(true/false) override CN to match pemFile certificate -- for development only.
* If the pemFile has the target server's certificate (instead of a CA Root certificate),
* instruct the TLS client to trust the CN value of the certificate in the pemFile,
Expand Down Expand Up @@ -487,6 +489,7 @@ public Orderer newOrderer(String name, String grpcURL) throws InvalidArgumentExc
* Supported properties
* <ul>
* <li>pemFile - File location for x509 pem certificate for SSL.</li>
* <li>pemBytes - byte array for x509 pem certificates for SSL</li>
* <li>trustServerCertificate - boolean(true/false) override CN to match pemFile certificate -- for development only.
* If the pemFile has the target server's certificate (instead of a CA Root certificate),
* instruct the TLS client to trust the CN value of the certificate in the pemFile,
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/org/hyperledger/fabric/sdk/Orderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import static java.lang.String.format;
import static org.hyperledger.fabric.sdk.helper.Utils.checkGrpcUrl;
import static org.hyperledger.fabric.sdk.helper.Utils.parseGrpcUrl;

/**
* The Orderer class represents a orderer to which SDK sends deploy, invoke, or query requests.
Expand Down Expand Up @@ -66,7 +67,7 @@ static Orderer createNewInstance(String name, String url, Properties properties)

byte[] getClientTLSCertificateDigest() {
if (null == clientTLSCertificateDigest) {
clientTLSCertificateDigest = new Endpoint(url, properties).getClientTLSCertificateDigest();
clientTLSCertificateDigest = Endpoint.createEndpoint(url, properties).getClientTLSCertificateDigest();
}
return clientTLSCertificateDigest;
}
Expand Down Expand Up @@ -145,7 +146,7 @@ Ab.BroadcastResponse sendTransaction(Common.Envelope transaction) throws Excepti
OrdererClient localOrdererClient = ordererClient;

if (localOrdererClient == null || !localOrdererClient.isChannelActive()) {
ordererClient = new OrdererClient(this, new Endpoint(url, properties).getChannelBuilder(), properties);
ordererClient = new OrdererClient(this, Endpoint.createEndpoint(url, properties).getChannelBuilder(), properties);
localOrdererClient = ordererClient;
}

Expand All @@ -170,7 +171,7 @@ DeliverResponse[] sendDeliver(Common.Envelope transaction) throws TransactionExc

logger.debug(format("Order.sendDeliver name: %s, url: %s", name, url));
if (localOrdererClient == null || !localOrdererClient.isChannelActive()) {
localOrdererClient = new OrdererClient(this, new Endpoint(url, properties).getChannelBuilder(), properties);
localOrdererClient = new OrdererClient(this, Endpoint.createEndpoint(url, properties).getChannelBuilder(), properties);
ordererClient = localOrdererClient;
}

Expand Down Expand Up @@ -200,6 +201,16 @@ synchronized void shutdown(boolean force) {

}

String endPoint;

String getEndpoint() {
if (null == endPoint) {
Properties properties = parseGrpcUrl(url);
endPoint = properties.get("host") + ":" + properties.getProperty("port").toLowerCase().trim();
}
return endPoint;
}

@Override
protected void finalize() throws Throwable {
shutdown(true);
Expand Down
Loading

0 comments on commit c5c31b8

Please sign in to comment.