From ef604d1fd3bc12eeed9910036f24e4a4953156c4 Mon Sep 17 00:00:00 2001 From: rickr Date: Mon, 20 Aug 2018 11:33:41 -0400 Subject: [PATCH] FAB-11570 Endorsing error Change-Id: I23a9098af6f440ee8584c0f78500f259a36b7835 Signed-off-by: rickr --- .../org/hyperledger/fabric/sdk/Channel.java | 136 ++-- .../fabric/sdk/EndorsementSelector.java | 24 - .../org/hyperledger/fabric/sdk/HFClient.java | 4 +- .../fabric/sdk/ServiceDiscovery.java | 592 +++++++++++++----- .../chaincodeendorsementpolicyAllMembers.yaml | 15 + .../fabric/sdk/ServiceDiscoveryTest.java | 359 +++++++++++ .../fabric/sdk/testutils/TestConfig.java | 5 - .../fabric/sdk/testutils/TestUtils.java | 4 +- .../sdkintegration/ServiceDiscoveryIT.java | 12 +- 9 files changed, 921 insertions(+), 230 deletions(-) delete mode 100644 src/main/java/org/hyperledger/fabric/sdk/EndorsementSelector.java create mode 100644 src/test/fixture/sdkintegration/chaincodeendorsementpolicyAllMembers.yaml create mode 100755 src/test/java/org/hyperledger/fabric/sdk/ServiceDiscoveryTest.java diff --git a/src/main/java/org/hyperledger/fabric/sdk/Channel.java b/src/main/java/org/hyperledger/fabric/sdk/Channel.java index 3cc7923d..a408711d 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/Channel.java +++ b/src/main/java/org/hyperledger/fabric/sdk/Channel.java @@ -1363,17 +1363,14 @@ public SDOrdererAddition setSDOrdererAddition(SDOrdererAddition sdOrdererAdditio } - static byte[] combineCerts(Collection... certCollections) throws IOException { + private static byte[] combineCerts(Collection... certCollections) throws IOException { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { for (Collection certCollection : certCollections) { for (byte[] cert : certCollection) { outputStream.write(cert); - } - } - return outputStream.toByteArray(); } } @@ -3176,6 +3173,28 @@ public Collection sendTransactionProposal(TransactionProposalR return sendProposal(transactionProposalRequest, getEndorsingPeers()); } + private static class PeerExactMatch { // use original equals of Peer and not what's overrident + final Peer peer; + + private PeerExactMatch(Peer peer) { + this.peer = peer; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PeerExactMatch)) { + return false; + } + + return peer == ((PeerExactMatch) obj).peer; + } + + @Override + public int hashCode() { + return System.identityHashCode(peer); + } + } + /** * Send a transaction proposal. * @@ -3247,30 +3266,45 @@ public Collection sendTransactionProposalToEndorsers(Transacti throw new ServiceDiscoveryException(format("Channel %s failed to find and endorsers for chaincode %s no layouts found.", name, chaincodeName)); } - SDChaindcode sdChaindcodeNI = new SDChaindcode(sdChaindcode); //copy. no ignored. + SDChaindcode sdChaindcodeEndorsementCopy = new SDChaindcode(sdChaindcode); //copy. no ignored. final boolean inspectResults = discoveryOptions.inspectResults; - if (sdChaindcodeNI.ignoreList(discoveryOptions.getIgnoreList()) < 1) { // apply ignore list + if (sdChaindcodeEndorsementCopy.ignoreList(discoveryOptions.getIgnoreList()) < 1) { // apply ignore list throw new ServiceDiscoveryException("Applying ignore list reduced to no available endorser options."); } if (IS_TRACE_LEVEL && null != discoveryOptions.getIgnoreList() && !discoveryOptions.getIgnoreList().isEmpty()) { - logger.trace(format("SDchaincode after ignore list: %s", sdChaindcodeNI)); + logger.trace(format("SDchaincode after ignore list: %s", sdChaindcodeEndorsementCopy)); } - final EndorsementSelector lendorsementSelector = discoveryOptions.endorsementSelector != null ? + final ServiceDiscovery.EndorsementSelector lendorsementSelector = discoveryOptions.endorsementSelector != null ? discoveryOptions.endorsementSelector : this.endorsementSelector; try { - final Map goodResponses = new HashMap<>(); // all good endorsements by endpoint - final Map allTried = new HashMap<>(); // all tried by endpoint + final Map goodResponses = new HashMap<>(); // all good endorsements by endpoint + final Map allTried = new HashMap<>(); // all tried by endpoint boolean done = false; int attempts = 1; //safety valve do { - logger.trace(format("Attempts: %d, chaincode discovery state: %s", attempts, sdChaindcodeNI)); - final SDEndorserState sdEndorserState = lendorsementSelector.endorserSelector(sdChaindcodeNI); + if (IS_TRACE_LEVEL) { + logger.trace(format("Attempts: %d, chaincode discovery state: %s", attempts, sdChaindcodeEndorsementCopy)); + } + final SDEndorserState sdEndorserState = lendorsementSelector.endorserSelector(sdChaindcodeEndorsementCopy); + + if (IS_TRACE_LEVEL) { + + StringBuilder sb = new StringBuilder(1000); + String sep = ""; + for (SDEndorser sdEndorser : sdEndorserState.getSdEndorsers()) { + sb.append(sep).append(sdEndorser); + sep = ", "; + } + + logger.trace(format("Attempts: %d, chaincode discovery state: %s. Endorser selector picked: %s. With selected endorsers: %s", attempts, sdChaindcodeEndorsementCopy.name, sdEndorserState.getPickedLayout(), sb.toString())); + + } Collection ep = sdEndorserState.getSdEndorsers(); ep = new ArrayList<>(ep); // just in case it's not already a copy @@ -3286,11 +3320,13 @@ public Collection sendTransactionProposalToEndorsers(Transacti } //Safety check make sure the selector isn't giving back endpoints to retry - ep.removeIf(sdEndorser -> goodResponses.keySet().contains(sdEndorser.getEndpoint())); + ep.removeIf(sdEndorser -> goodResponses.keySet().contains(sdEndorser)); if (ep.isEmpty()) { // this would be odd but lets go with it. - Set needed = sdChaindcode.meetsEndorsmentPolicy(goodResponses.keySet()); - if (needed != null) { + logger.debug(format("Channel %s, chaincode %s attempts: %d endorser selector returned no additional endorements needed.", name, chaincodeName, attempts)); + + Collection needed = sdChaindcode.meetsEndorsmentPolicy(goodResponses.keySet()); + if (needed != null) { // means endorsment meet with those in the needed. ArrayList ret = new ArrayList<>(needed.size()); needed.forEach(s -> ret.add(goodResponses.get(s))); @@ -3319,16 +3355,17 @@ public Collection sendTransactionProposalToEndorsers(Transacti } } - HashMap stringPeerHashMap = new HashMap<>(peerEndpointMap); - ArrayList endorsers = new ArrayList<>(ep.size()); - for (SDEndorser endpoint : ep) { + Map lpeerEndpointMap = new HashMap<>(peerEndpointMap); + Map endorsers = new HashMap<>(ep.size()); + Map peer2sdEndorser = new HashMap<>(ep.size()); + for (SDEndorser sdEndorser : ep) { - Peer epeer = stringPeerHashMap.get(endpoint.getEndpoint()); + Peer epeer = lpeerEndpointMap.get(sdEndorser.getEndpoint()); if (epeer != null && !epeer.hasConnected()) { // mostly because gossip may have malicious data so if we've not connected update TLS props from chaincode discovery. final Properties properties = epeer.getProperties(); - final byte[] bytes = combineCerts(endpoint.getTLSCerts(), endpoint.getTLSIntermediateCerts()); + final byte[] bytes = combineCerts(sdEndorser.getTLSCerts(), sdEndorser.getTLSIntermediateCerts()); properties.put("pemBytes", bytes); epeer.setProperties(properties); @@ -3337,12 +3374,12 @@ public Collection sendTransactionProposalToEndorsers(Transacti @Override public String getMspId() { - return endpoint.getMspid(); + return sdEndorser.getMspid(); } @Override public String getEndpoint() { - return endpoint.getEndpoint(); + return sdEndorser.getEndpoint(); } @Override @@ -3358,12 +3395,12 @@ public HFClient getClient() { @Override public byte[][] getTLSCerts() { - return endpoint.getTLSCerts().toArray(new byte[endpoint.getTLSCerts().size()][]); + return sdEndorser.getTLSCerts().toArray(new byte[sdEndorser.getTLSCerts().size()][]); } @Override public byte[][] getTLSIntermediateCerts() { - return endpoint.getTLSIntermediateCerts().toArray(new byte[endpoint.getTLSIntermediateCerts().size()][]); + return sdEndorser.getTLSIntermediateCerts().toArray(new byte[sdEndorser.getTLSIntermediateCerts().size()][]); } @Override @@ -3372,36 +3409,37 @@ public Map getEndpointMap() { } }); } - endorsers.add(epeer); + endorsers.put(sdEndorser, epeer); + peer2sdEndorser.put(new PeerExactMatch(epeer), sdEndorser); // reverse } - final Collection proposalResponses = sendProposalToPeers(endorsers, invokeProposal, transactionContext); - HashSet loopGood = new HashSet<>(); - HashSet loopBad = new HashSet<>(); + final Collection proposalResponses = sendProposalToPeers(endorsers.values(), invokeProposal, transactionContext); + HashSet loopGood = new HashSet<>(); + HashSet loopBad = new HashSet<>(); for (ProposalResponse proposalResponse : proposalResponses) { - final String endpoint = proposalResponse.getPeer().getEndpoint(); - allTried.put(endpoint, proposalResponse); + final SDEndorser sdEndorser = peer2sdEndorser.get(new PeerExactMatch(proposalResponse.getPeer())); + allTried.put(sdEndorser, proposalResponse); final ChaincodeResponse.Status status = proposalResponse.getStatus(); if (ChaincodeResponse.Status.SUCCESS.equals(status)) { - goodResponses.put(endpoint, proposalResponse); - logger.trace(format("Channel %s, chaincode %s attempts %d good endorsements: %s", name, chaincodeName, attempts, endpoint)); - loopGood.add(endpoint); + goodResponses.put(sdEndorser, proposalResponse); + logger.trace(format("Channel %s, chaincode %s attempts %d good endorsements: %s", name, chaincodeName, attempts, sdEndorser)); + loopGood.add(sdEndorser); } else { - logger.debug(format("Channel %s, chaincode %s attempts %d bad endorsements: %s", name, chaincodeName, attempts, endpoint)); - loopBad.add(endpoint); + logger.debug(format("Channel %s, chaincode %s attempts %d bad endorsements: %s", name, chaincodeName, attempts, sdEndorser)); + loopBad.add(sdEndorser); } } //Always check on original - Set needed = sdChaindcode.meetsEndorsmentPolicy(goodResponses.keySet()); - if (needed != null) { - ArrayList ret = new ArrayList<>(needed.size()); - needed.forEach(s -> ret.add(goodResponses.get(s))); + Collection required = sdChaindcode.meetsEndorsmentPolicy(goodResponses.keySet()); + if (required != null) { + ArrayList ret = new ArrayList<>(required.size()); + required.forEach(s -> ret.add(goodResponses.get(s))); if (IS_DEBUG_LEVEL) { @@ -3410,26 +3448,22 @@ public Map getEndpointMap() { for (ProposalResponse proposalResponse : ret) { sb.append(sep).append(proposalResponse.getPeer()); sep = ", "; - } - logger.debug(format("Channel %s, chaincode %s got all needed endorsements: %s", name, chaincodeName, sb.toString())); - } - return ret; // the happy path :)! } else { //still don't have the needed endorsements. - sdChaindcodeNI.endorsedList(loopGood); + sdChaindcodeEndorsementCopy.endorsedList(loopGood); // mark the good ones in the working copy. - if (sdChaindcodeNI.ignoreList(loopBad) < 1) { // apply ignore list + if (sdChaindcodeEndorsementCopy.ignoreListSDEndorser(loopBad) < 1) { // apply ignore list done = true; // no more layouts } } } while (!done && ++attempts <= 5); - logger.trace(format("Endorsements not achieved chaincode: %s, done: %b, attempts: %d", chaincodeName, done, attempts)); + logger.debug(format("Endorsements not achieved chaincode: %s, done: %b, attempts: %d", chaincodeName, done, attempts)); if (inspectResults) { return allTried.values(); } else { @@ -3598,10 +3632,10 @@ private Collection sendProposal(TransactionRequest proposalReq } } - private transient EndorsementSelector endorsementSelector = ServiceDiscovery.DEFAULT_ENDORSEMENT_SELECTION; + private transient ServiceDiscovery.EndorsementSelector endorsementSelector = ServiceDiscovery.DEFAULT_ENDORSEMENT_SELECTION; - public EndorsementSelector setSDEndorserSelector(EndorsementSelector endorsementSelector) { - EndorsementSelector ret = this.endorsementSelector; + public ServiceDiscovery.EndorsementSelector setSDEndorserSelector(ServiceDiscovery.EndorsementSelector endorsementSelector) { + ServiceDiscovery.EndorsementSelector ret = this.endorsementSelector; this.endorsementSelector = endorsementSelector; return ret; @@ -4204,7 +4238,7 @@ List getCollections() { */ public static class DiscoveryOptions { Set ignoreList = new HashSet<>(); - EndorsementSelector endorsementSelector = null; + ServiceDiscovery.EndorsementSelector endorsementSelector = null; boolean inspectResults = false; boolean forceDiscovery = false; @@ -4245,7 +4279,7 @@ public DiscoveryOptions setInspectResults(boolean inspectResults) { * @return * @throws InvalidArgumentException */ - public DiscoveryOptions setEndorsementSelector(EndorsementSelector endorsementSelector) throws InvalidArgumentException { + public DiscoveryOptions setEndorsementSelector(ServiceDiscovery.EndorsementSelector endorsementSelector) throws InvalidArgumentException { if (endorsementSelector == null) { throw new InvalidArgumentException("endorsementSelector parameter is null."); } diff --git a/src/main/java/org/hyperledger/fabric/sdk/EndorsementSelector.java b/src/main/java/org/hyperledger/fabric/sdk/EndorsementSelector.java deleted file mode 100644 index cbf4a5e9..00000000 --- a/src/main/java/org/hyperledger/fabric/sdk/EndorsementSelector.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * - * 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; -} diff --git a/src/main/java/org/hyperledger/fabric/sdk/HFClient.java b/src/main/java/org/hyperledger/fabric/sdk/HFClient.java index f7147a97..da82fd6b 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/HFClient.java +++ b/src/main/java/org/hyperledger/fabric/sdk/HFClient.java @@ -527,10 +527,10 @@ public Orderer newOrderer(String name, String grpcURL, Properties properties) th } /** - * Query the channels for peers + * Query the joined channels for peers * * @param peer the peer to query - * @return A set of strings with the peer names. + * @return A set of strings with the names of the channels the peer has joined. * @throws InvalidArgumentException * @throws ProposalException */ diff --git a/src/main/java/org/hyperledger/fabric/sdk/ServiceDiscovery.java b/src/main/java/org/hyperledger/fabric/sdk/ServiceDiscovery.java index 721cab97..ae3d88cb 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/ServiceDiscovery.java +++ b/src/main/java/org/hyperledger/fabric/sdk/ServiceDiscovery.java @@ -22,10 +22,10 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.Executors; @@ -42,17 +42,22 @@ import org.hyperledger.fabric.protos.msp.Identities; import org.hyperledger.fabric.protos.msp.MspConfig; import org.hyperledger.fabric.sdk.Channel.ServiceDiscoveryChaincodeCalls; +import org.hyperledger.fabric.sdk.ServiceDiscovery.SDLayout.SDGroup; import org.hyperledger.fabric.sdk.exception.InvalidProtocolBufferRuntimeException; import org.hyperledger.fabric.sdk.exception.ServiceDiscoveryException; import org.hyperledger.fabric.sdk.helper.Config; +import org.hyperledger.fabric.sdk.helper.DiagnosticFileDumper; import org.hyperledger.fabric.sdk.transaction.TransactionContext; import static java.lang.String.format; -class ServiceDiscovery { +public class ServiceDiscovery { private static final Log logger = LogFactory.getLog(ServiceDiscovery.class); private static final boolean DEBUG = logger.isDebugEnabled(); private static final Config config = Config.getConfig(); + private static final boolean IS_TRACE_LEVEL = logger.isTraceEnabled(); + private static final DiagnosticFileDumper diagnosticFileDumper = IS_TRACE_LEVEL + ? config.getDiagnosticFileDumper() : null; private static final int SERVICE_DISCOVERY_WAITTIME = config.getServiceDiscoveryWaitTime(); private static final Random random = new Random(); private final Collection serviceDiscoveryPeers; @@ -236,7 +241,18 @@ SDNetwork networkDiscovery(TransactionContext ltransactionContext, boolean force Protocol.SignedRequest sr = Protocol.SignedRequest.newBuilder() .setPayload(payloadBytes).setSignature(signatureBytes).build(); + if (IS_TRACE_LEVEL && null != diagnosticFileDumper) { // dump protobuf we sent + logger.trace(format("Service discovery channel %s %s service chaincode query sent %s", channelName, serviceDiscoveryPeer, + diagnosticFileDumper.createDiagnosticProtobufFile(sr.toByteArray()))); + } + final Protocol.Response response = serviceDiscoveryPeer.sendDiscoveryRequestAsync(sr).get(SERVICE_DISCOVERY_WAITTIME, TimeUnit.MILLISECONDS); + + if (IS_TRACE_LEVEL && null != diagnosticFileDumper) { // dump protobuf we get + logger.trace(format("Service discovery channel %s %s service discovery returned %s", channelName, serviceDiscoveryPeer, + diagnosticFileDumper.createDiagnosticProtobufFile(response.toByteArray()))); + } + serviceDiscoveryPeer.hasConnected(); final List resultsList = response.getResultsList(); Protocol.QueryResult queryResult; @@ -291,7 +307,7 @@ SDNetwork networkDiscovery(TransactionContext ltransactionContext, boolean force Protocol.Endpoints value = i.getValue(); for (Protocol.Endpoint l : value.getEndpointList()) { logger.trace(format("Channel %s discovered orderer MSPID: %s, endpoint: %s:%s", channelName, mspid, l.getHost(), l.getPort())); - String endpoint = remapEndpoint((l.getHost() + ":" + l.getPort()).trim().toLowerCase()); + String endpoint = (l.getHost() + ":" + l.getPort()).trim().toLowerCase(); final SDOrderer sdOrderer = new SDOrderer(mspid, endpoint, lsdNetwork.getTlsCerts(mspid), lsdNetwork.getTlsIntermediateCerts(mspid)); @@ -441,20 +457,34 @@ Map discoverEndorserEndpoints(TransactionContext transacti ByteString signatureBytes = ltransactionContext.signByteStrings(payloadBytes); Protocol.SignedRequest sr = Protocol.SignedRequest.newBuilder() .setPayload(payloadBytes).setSignature(signatureBytes).build(); + if (IS_TRACE_LEVEL && null != diagnosticFileDumper) { // dump protobuf we sent + logger.trace(format("Service discovery channel %s %s service chaincode query sent %s", channelName, serviceDiscoveryPeer, + diagnosticFileDumper.createDiagnosticProtobufFile(sr.toByteArray()))); + } logger.debug(format("Channel %s peer %s sending chaincode query request", channelName, serviceDiscoveryPeer.toString())); final Protocol.Response response = serviceDiscoveryPeer.sendDiscoveryRequestAsync(sr).get(SERVICE_DISCOVERY_WAITTIME, TimeUnit.MILLISECONDS); + if (IS_TRACE_LEVEL && null != diagnosticFileDumper) { // dump protobuf we get + logger.trace(format("Service discovery channel %s %s service chaincode query returned %s", channelName, serviceDiscoveryPeer, + diagnosticFileDumper.createDiagnosticProtobufFile(response.toByteArray()))); + } logger.debug(format("Channel %s peer %s completed chaincode query request", channelName, serviceDiscoveryPeer.toString())); serviceDiscoveryPeer.hasConnected(); for (Protocol.QueryResult queryResult : response.getResultsList()) { if (queryResult.getResultCase().getNumber() == Protocol.QueryResult.ERROR_FIELD_NUMBER) { - throw new ServiceDiscoveryException(format("Error %s", queryResult.getError().getContent())); + + ServiceDiscoveryException discoveryException = new ServiceDiscoveryException(format("Error %s", queryResult.getError().getContent())); + logger.error(discoveryException.getMessage()); + continue; } if (queryResult.getResultCase().getNumber() != Protocol.QueryResult.CC_QUERY_RES_FIELD_NUMBER) { - throw new ServiceDiscoveryException(format("Error expected chaincode endorsement query but got %s : ", queryResult.getResultCase().toString())); + ServiceDiscoveryException discoveryException = new ServiceDiscoveryException(format("Error expected chaincode endorsement query but got %s : ", queryResult.getResultCase().toString())); + logger.error(discoveryException.getMessage()); + continue; + } Protocol.ChaincodeQueryResult ccQueryRes = queryResult.getCcQueryRes(); @@ -466,11 +496,18 @@ Map discoverEndorserEndpoints(TransactionContext transacti final String chaincode = es.getChaincode(); List layouts = new LinkedList<>(); for (Protocol.Layout layout : es.getLayoutsList()) { + SDLayout sdLayout = null; Map quantitiesByGroupMap = layout.getQuantitiesByGroupMap(); for (Map.Entry qmap : quantitiesByGroupMap.entrySet()) { final String key = qmap.getKey(); final int quantity = qmap.getValue(); + if (quantity < 1) { + continue; + } Protocol.Peers peers = es.getEndorsersByGroupsMap().get(key); + if (peers == null || peers.getPeersCount() == 0) { + continue; + } List sdEndorsers = new LinkedList<>(); @@ -496,7 +533,11 @@ Map discoverEndorserEndpoints(TransactionContext transacti sdEndorsers.add(nppp); } - layouts.add(new SDLayout(quantity, sdEndorsers)); + if (sdLayout == null) { + sdLayout = new SDLayout(); + layouts.add(sdLayout); + } + sdLayout.addGroup(key, quantity, sdEndorsers); } } if (layouts.isEmpty()) { @@ -508,20 +549,15 @@ Map discoverEndorserEndpoints(TransactionContext transacti sb.append("Channel ").append(channelName) .append(" found ").append(layouts.size()).append(" layouts for chaincode: ").append(es.getChaincode()); - String sep = " "; + sb.append(", layouts: ["); + + String sep = ""; for (SDLayout layout : layouts) { - sb.append(sep) - .append("SDLayout[") - .append("required: ").append(layout.getRequired()).append(", endorsers: ["); - - String sep2 = ""; - for (SDEndorser sdEndorser : layout.getSDEndorsers()) { - sb.append(sep2).append(sdEndorser.toString()); - sep2 = ", "; - } - sb.append("]"); + sb.append(sep).append(layout); + sep = ", "; } + sb.append("]"); logger.debug(sb.toString()); } @@ -562,48 +598,163 @@ Map discoverEndorserEndpoints(TransactionContext transacti static final EndorsementSelector ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT = sdChaindcode -> { List layouts = sdChaindcode.getLayouts(); - SDLayout pickedLayout = layouts.get(0); + class LGroup { // local book keeping. + int stillRequred; + final Set endorsers = new HashSet<>(); - if (layouts.size() > 1) { // pick layout by least number of endorsers .. least number of peers hit and smaller block! + LGroup(SDGroup group) { + endorsers.addAll(group.getEndorsers()); + this.stillRequred = group.getStillRequired(); + } - ArrayList leastEndorsers = new ArrayList<>(); - for (SDLayout sdLayout : layouts) { + // return true if still required + boolean endorsed(Set endorsed) { + for (SDEndorser sdEndorser : endorsed) { + if (endorsers.contains(sdEndorser)) { + endorsers.remove(sdEndorser); + stillRequred = Math.max(0, stillRequred - 1); + } + } + return stillRequred > 0; + + } + } + + SDLayout pickedLayout = null; - if (leastEndorsers.size() == 0 || leastEndorsers.get(0).getStillRequired() > sdLayout.getStillRequired()) { - leastEndorsers = new ArrayList<>(); - leastEndorsers.add(sdLayout); - } else if (leastEndorsers.get(0).getStillRequired() == sdLayout.getStillRequired()) { - leastEndorsers.add(sdLayout); + Map> layoutEndorsers = new HashMap<>(); + // if (layouts.size() > 1) { // pick layout by least number of endorsers .. least number of peers hit and smaller block! + + for (SDLayout sdLayout : layouts) { + + Set remainingGroups = new HashSet<>(); + for (SDGroup sdGroup : sdLayout.getSDLGroups()) { + remainingGroups.add(new LGroup(sdGroup)); + } + // These are required as there is no choice. + Set required = new HashSet<>(); + for (LGroup lgroup : remainingGroups) { + if (lgroup.stillRequred == lgroup.endorsers.size()) { + required.addAll(lgroup.endorsers); } } - if (leastEndorsers.size() == 1) { - pickedLayout = leastEndorsers.get(0); - } else { - long maxHeight = -1L; // go with the highest total block height of the required. + //add those that there are no choice. + + if (required.size() > 0) { + Set remove = new HashSet<>(remainingGroups.size()); + for (LGroup lGroup : remainingGroups) { + if (!lGroup.endorsed(required)) { + remove.add(lGroup); + } + } + remainingGroups.removeAll(remove); + Set sdEndorsers = layoutEndorsers.computeIfAbsent(sdLayout, k -> new HashSet<>()); + sdEndorsers.addAll(required); + } + + if (remainingGroups.isEmpty()) { // no more groups here done for this layout. + continue; // done with this layout there really were no choices. + } + + //Now go through groups finding which endorsers can satisfy the most groups. + + do { + + Map matchCount = new HashMap<>(); + + for (LGroup group : remainingGroups) { + for (SDEndorser sdEndorser : group.endorsers) { + Integer count = matchCount.get(sdEndorser); + if (count == null) { + matchCount.put(sdEndorser, 1); + } else { + matchCount.put(sdEndorser, ++count); + } + } + } - for (SDLayout layout : leastEndorsers) { // means required was the same. - List sdEndorsers = topNbyHeight(layout.getStillRequired(), layout.getSDEndorsers()); - long score = 0; - for (SDEndorser sdEndorser : sdEndorsers) { - score += sdEndorser.getLedgerHeight(); + Set theMost = new HashSet<>(); + int maxMatch = 0; + for (Map.Entry m : matchCount.entrySet()) { + int count = m.getValue(); + SDEndorser sdEndorser = m.getKey(); + if (count > maxMatch) { + theMost.clear(); + theMost.add(sdEndorser); + maxMatch = count; + } else if (count == maxMatch) { + theMost.add(sdEndorser); } - if (score > maxHeight) { - maxHeight = score; - pickedLayout = layout; + } + + Set theVeryMost = new HashSet<>(1); + long max = 0L; + // Tie breaker: Pick one with greatest ledger height. + for (SDEndorser sd : theMost) { + if (sd.getLedgerHeight() > max) { + max = sd.getLedgerHeight(); + theVeryMost.clear(); + theVeryMost.add(sd); } + + } + + Set remove2 = new HashSet<>(remainingGroups.size()); + for (LGroup lGroup : remainingGroups) { + if (!lGroup.endorsed(theVeryMost)) { + remove2.add(lGroup); + } + } + Set sdEndorsers = layoutEndorsers.computeIfAbsent(sdLayout, k -> new HashSet<>()); + sdEndorsers.addAll(theVeryMost); + remainingGroups.removeAll(remove2); + + } while (!remainingGroups.isEmpty()); + + // Now pick the layout with least endorsers + + } + //Pick layout which needs least endorsements. + int min = Integer.MAX_VALUE; + Set theLeast = new HashSet<>(); + + for (Map.Entry> l : layoutEndorsers.entrySet()) { + SDLayout sdLayoutK = l.getKey(); + Integer count = l.getValue().size(); + if (count < min) { + theLeast.clear(); + theLeast.add(sdLayoutK); + min = count; + } else if (count == min) { + theLeast.add(sdLayoutK); + } + } + + if (theLeast.size() == 1) { + pickedLayout = theLeast.iterator().next(); + + } else { + long max = 0L; + // Tie breaker: Pick one with greatest ledger height. + for (SDLayout sdLayout : theLeast) { + int height = 0; + for (SDEndorser sdEndorser : layoutEndorsers.get(sdLayout)) { + height += sdEndorser.getLedgerHeight(); + } + if (height > max) { + max = height; + pickedLayout = sdLayout; } } } - List top = topNbyHeight(pickedLayout.getStillRequired(), pickedLayout.getSDEndorsers()); - ArrayList retlist = new ArrayList<>(pickedLayout.getStillRequired()); - retlist.addAll(top); final SDEndorserState sdEndorserState = new SDEndorserState(); - sdEndorserState.setPickedEndorsers(retlist); + sdEndorserState.setPickedEndorsers(layoutEndorsers.get(pickedLayout)); sdEndorserState.setPickedLayout(pickedLayout); return sdEndorserState; + }; public static final EndorsementSelector DEFAULT_ENDORSEMENT_SELECTION = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; @@ -616,29 +767,30 @@ Map discoverEndorserEndpoints(TransactionContext transacti SDLayout pickedLayout = layouts.get(0); - if (layouts.size() > 1) { + if (layouts.size() > 1) { // more than one pick a random one. pickedLayout = layouts.get(random.nextInt(layouts.size())); } - List pickedEndorsers = pickedLayout.getSDEndorsers(); - final int required = pickedLayout.getStillRequired(); + Map retMap = new HashMap<>(); //hold results. - if (required != pickedEndorsers.size()) { - List shuffle = new ArrayList<>(pickedEndorsers); - Collections.shuffle(shuffle); - pickedEndorsers = shuffle.subList(0, required); + for (SDGroup group : pickedLayout.getSDLGroups()) { // go through groups getting random required endorsers + List endorsers = new ArrayList<>(group.getEndorsers()); + int required = group.getStillRequired(); // what's needed in that group. + Collections.shuffle(endorsers); // randomize. + List sdEndorsers = endorsers.subList(0, required); // pick top endorsers. + sdEndorsers.forEach(sdEndorser -> { + retMap.putIfAbsent(sdEndorser.getEndpoint(), sdEndorser); // put if endpoint is not in there already. + }); } - List retlist = new ArrayList<>(required); - retlist.addAll(pickedEndorsers); - final SDEndorserState sdEndorserState = new SDEndorserState(); - sdEndorserState.setPickedEndorsers(retlist); + final SDEndorserState sdEndorserState = new SDEndorserState(); //returned result. + sdEndorserState.setPickedEndorsers(retMap.values()); sdEndorserState.setPickedLayout(pickedLayout); return sdEndorserState; }; - static class SDChaindcode { + public static class SDChaindcode { final String name; final List layouts; @@ -654,10 +806,11 @@ static class SDChaindcode { this.layouts = layouts; } - List getLayouts() { - return layouts; + public List getLayouts() { + return Collections.unmodifiableList(layouts); } + // returns number of layouts left. int ignoreList(Collection names) { if (names != null && !names.isEmpty()) { layouts.removeIf(sdLayout -> !sdLayout.ignoreList(names)); @@ -665,23 +818,34 @@ int ignoreList(Collection names) { return layouts.size(); } - void endorsedList(Collection names) { - if (!names.isEmpty()) { - for (SDLayout sdLayout : layouts) { - sdLayout.endorsedList(names); + int ignoreListSDEndorser(Collection sdEndorsers) { + if (sdEndorsers != null && !sdEndorsers.isEmpty()) { + layouts.removeIf(sdLayout -> !sdLayout.ignoreListSDEndorser(sdEndorsers)); + } + return layouts.size(); + } + + boolean endorsedList(Collection sdEndorsers) { + boolean ret = false; + + for (SDLayout sdLayout : layouts) { + if (sdLayout.endorsedList(sdEndorsers)) { + ret = true; } } + return ret; } - // return the set - Set meetsEndorsmentPolicy(Set endpoints) { + // return the set needed or null if the policy was not meet. + Collection meetsEndorsmentPolicy(Set endpoints) { - Set ret = null; + Collection ret = null; // not meet. for (SDLayout sdLayout : layouts) { - final Set needed = sdLayout.meetsEndorsmentPolicy(endpoints); + final Collection needed = sdLayout.meetsEndorsmentPolicy(endpoints); + if (needed != null && (ret == null || ret.size() > needed.size())) { - ret = needed; + ret = needed; // needed is less so lets go with that. } } return ret; @@ -708,104 +872,237 @@ public String toString() { public static class SDLayout { - final List sdEndorsers; - final int required; - int endorsed = 0; + final List groups = new LinkedList<>(); + + SDLayout() { + + } + + //Copy constructor + SDLayout(SDLayout sdLayout) { + for (SDGroup group : sdLayout.groups) { + new SDGroup(group); + } + } @Override public String toString() { StringBuilder sb = new StringBuilder(1000); - sb.append("SDLayout(") - .append("required: ").append(getRequired()).append(", endorsed:").append(endorsed); + sb.append("SDLayout: {"); - if (sdEndorsers != null && !sdEndorsers.isEmpty()) { - sb.append(", endorsers: ["); + if (!groups.isEmpty()) { + sb.append("groups: ["); String sep2 = ""; - for (SDEndorser sdEndorser : getSDEndorsers()) { - sb.append(sep2).append(sdEndorser.toString()); + for (SDGroup group : groups) { + sb.append(sep2).append(group.toString()); sep2 = ", "; } sb.append("]"); + } else { + sb.append(", groups: []"); } - sb.append(")"); + sb.append("}"); return sb.toString(); } - SDLayout(int quantity, List protocolPeers) { - required = quantity; - this.sdEndorsers = protocolPeers; + //return true if the groups still exist to get endorsement. + boolean ignoreList(Collection names) { + boolean ret = true; + HashSet bnames = new HashSet<>(names); + + for (SDGroup group : groups) { + if (!group.ignoreList(bnames)) { + ret = false; // group can no longer be satisfied. + } + } + return ret; } - SDLayout(SDLayout sdLayout) { - required = sdLayout.required; - this.sdEndorsers = new LinkedList<>(sdLayout.sdEndorsers); - endorsed = 0; + boolean ignoreListSDEndorser(Collection names) { + boolean ret = true; + HashSet bnames = new HashSet<>(names); + + for (SDGroup group : groups) { + if (!group.ignoreListSDEndorser(bnames)) { + ret = false; // group can no longer be satisfied. + } + } + return ret; } - public int getRequired() { - return required; + // endorsement has been meet. + boolean endorsedList(Collection sdEndorsers) { + + int endorsementMeet = 0; + for (SDGroup group : groups) { + if (group.endorsedList(sdEndorsers)) { + ++endorsementMeet; + } + } + return endorsementMeet >= groups.size(); } - List getSDEndorsers() { - return sdEndorsers; + // Returns null when not meet and endorsers needed if it is. + Collection meetsEndorsmentPolicy(Set endpoints) { + Set ret = new HashSet<>(); + + for (SDGroup group : groups) { + Collection sdEndorsers = group.meetsEndorsmentPolicy(endpoints, null); + if (null == sdEndorsers) { + return null; // group was not satisfied + } + ret.addAll(sdEndorsers); // add all these endorsers. + } + + return ret; } - int getStillRequired() { - return required - endorsed; + public Collection getSDLGroups() { + return new ArrayList<>(groups); + } - boolean ignoreList(Collection names) { - HashSet bnames = new HashSet<>(names); - for (Iterator i = sdEndorsers.iterator(); i.hasNext(); - ) { //checkstyle oddity. - final SDEndorser endorser = i.next(); - if (bnames.contains(endorser.getEndpoint())) { - i.remove(); - if (sdEndorsers.size() < getStillRequired()) { - return false; // not enough endorsers - } + public class SDGroup { + final int required; // the number that's needed for the group to be endorsed. + final List endorsers = new LinkedList<>(); + private final String name; // name of the groups - just for debug sake. + private int endorsed = 0; // number that have been now endorsed. + + { + SDLayout.this.groups.add(this); + } + + SDGroup(String name, int required, List endorsers) { + this.name = name; + this.required = required; + this.endorsers.addAll(endorsers); + + } + + SDGroup(SDGroup group) { //copy constructor + name = group.name; + required = group.required; + endorsers.addAll(group.endorsers); + endorsed = 0; // on copy reset to no endorsements + } + + public int getStillRequired() { + return required - endorsed; + } + + public String getName() { + return name; + } + + public int getRequired() { + return required; + } + + public Collection getEndorsers() { + return new ArrayList<>(endorsers); + } + + //returns true if there are still sufficent endorsers for this group. + boolean ignoreList(Collection names) { + HashSet bnames = new HashSet<>(names); + endorsers.removeIf(endorser -> bnames.contains(endorser.getEndpoint())); + return endorsers.size() >= required; + } + + //returns true if there are still sufficent endorsers for this group. + boolean ignoreListSDEndorser(Collection sdEndorsers) { + HashSet bnames = new HashSet<>(sdEndorsers); + endorsers.removeIf(endorser -> bnames.contains(endorser)); + return endorsers.size() >= required; + } + + // retrun true if th endorsements have been meet. + boolean endorsedList(Collection sdEndorsers) { + //This is going to look odd so here goes: Service discovery can't guarantee the endpoint certs are valid + // and so there may be multiple endpoints with different MSP ids. However if we have gotten an + // endorsement from an endpoint that means it's been satisfied and can be removed. + + if (endorsed >= required) { + return true; + } + if (!sdEndorsers.isEmpty()) { + final Set enames = new HashSet<>(sdEndorsers.size()); + sdEndorsers.forEach(sdEndorser -> enames.add(sdEndorser.getEndpoint())); + + endorsers.removeIf(endorser -> { + if (enames.contains(endorser.getEndpoint())) { + endorsed = Math.min(required, endorsed++); + return true; // remove it. + } + return false; // needs to stay in the list. + }); } + + return endorsed >= required; } - return true; - } - void endorsedList(Collection names) { - HashSet bnames = new HashSet<>(names); - for (Iterator i = sdEndorsers.iterator(); i.hasNext(); - ) { //checkstyle oddity. - final SDEndorser endorser = i.next(); - if (bnames.contains(endorser.getEndpoint())) { - i.remove(); - ++endorsed; + @Override + public String toString() { + StringBuilder sb = new StringBuilder(512); + sb.append("SDGroup: { name: ").append(name).append(", required: ").append(required); + + if (!endorsers.isEmpty()) { + sb.append(", endorsers: ["); + String sep2 = ""; + for (SDEndorser sdEndorser : endorsers) { + sb.append(sep2).append(sdEndorser.toString()); + sep2 = ", "; + } + sb.append("]"); + } else { + sb.append(", endorsers: []"); } + sb.append("}"); + return sb.toString(); } - } - Set meetsEndorsmentPolicy(Set endpoints) { - Set needed = new HashSet<>(); - for (SDEndorser sdEndorser : sdEndorsers) { - if (endpoints.contains(sdEndorser.getEndpoint())) { - needed.add(sdEndorser.getEndpoint()); - if (needed.size() >= required) { - return needed; + // Returns + Collection meetsEndorsmentPolicy(Set allEndorsed, Collection requiredYet) { + + Set ret = new HashSet<>(this.endorsers.size()); + for (SDEndorser hasBeenEndorsed : allEndorsed) { + for (SDEndorser sdEndorser : endorsers) { + if (hasBeenEndorsed.equals(sdEndorser)) { + ret.add(sdEndorser); + if (ret.size() >= required) { + return ret; // got what we needed. + } + } } } + if (null != requiredYet) { + for (SDEndorser sdEndorser : endorsers) { + if (!allEndorsed.contains(sdEndorser)) { + requiredYet.add(sdEndorser); + } + } + } + return null; // group has not meet endorsement. } - return null; + } + + void addGroup(String key, int required, List endorsers) { + new SDGroup(key, required, endorsers); + } } public static class SDEndorserState { - Collection sdEndorsers = new ArrayList<>(); + private Collection sdEndorsers = new ArrayList<>(); private SDLayout pickedLayout; - void setPickedEndorsers(Collection sdEndorsers) { + public void setPickedEndorsers(Collection sdEndorsers) { this.sdEndorsers = sdEndorsers; } @@ -814,7 +1111,7 @@ Collection getSdEndorsers() { return sdEndorsers; } - void setPickedLayout(SDLayout pickedLayout) { + public void setPickedLayout(SDLayout pickedLayout) { this.pickedLayout = pickedLayout; } @@ -833,6 +1130,11 @@ public static class SDEndorser { private final Collection tlsCerts; private final Collection tlsIntermediateCerts; + SDEndorser() { // for testing only + tlsCerts = null; + tlsIntermediateCerts = null; + } + SDEndorser(Protocol.Peer peerRet, Collection tlsCerts, Collection tlsIntermediateCerts) { this.tlsCerts = tlsCerts; this.tlsIntermediateCerts = tlsIntermediateCerts; @@ -842,11 +1144,11 @@ public static class SDEndorser { parseIdentity(peerRet); } - public Collection getTLSCerts() { + Collection getTLSCerts() { return tlsCerts; } - public Collection getTLSIntermediateCerts() { + Collection getTLSIntermediateCerts() { return tlsIntermediateCerts; } @@ -868,7 +1170,7 @@ private void parseIdentity(Protocol.Peer peerRet) { } } - String parseEndpoint(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeException { + private String parseEndpoint(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeException { if (null == endPoint) { try { @@ -882,7 +1184,7 @@ String parseEndpoint(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeE Message.AliveMessage aliveMsg = gossipMessageMemberInfo.getAliveMsg(); endPoint = aliveMsg.getMembership().getEndpoint(); if (endPoint != null) { - endPoint = remapEndpoint(endPoint.toLowerCase().trim()); //makes easier on comparing. + endPoint = endPoint.toLowerCase().trim(); //makes easier on comparing. } } catch (InvalidProtocolBufferException e) { throw new InvalidProtocolBufferRuntimeException(e); @@ -893,7 +1195,7 @@ String parseEndpoint(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeE } - long parseLedgerHeight(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeException { + private long parseLedgerHeight(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntimeException { if (-1L == ledgerHeight) { try { @@ -916,6 +1218,25 @@ long parseLedgerHeight(Protocol.Peer peerRet) throws InvalidProtocolBufferRuntim return ledgerHeight; } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof SDEndorser)) { + return false; + } + SDEndorser other = (SDEndorser) obj; + return Objects.equals(mspid, other.getMspid()) && Objects.equals(endPoint, other.getEndpoint()); + } + + @Override + public int hashCode() { + + return Objects.hash(mspid, endPoint); + } + Set getChaincodeNames() { if (chaincodesList == null) { return Collections.emptySet(); @@ -938,7 +1259,7 @@ public String toString() { } - static List topNbyHeight(int required, List endorsers) { + private static List topNbyHeight(int required, List endorsers) { ArrayList ret = new ArrayList<>(endorsers); ret.sort(Comparator.comparingLong(SDEndorser::getLedgerHeight)); return ret.subList(Math.max(ret.size() - required, 0), ret.size()); @@ -1028,21 +1349,10 @@ protected void finalize() throws Throwable { super.finalize(); } - private static final String REMAP2HOST = System.getProperty("org.hyperledger.fabric.sdk.test.endpoint_remap_discovery_host_name"); - - private static String remapEndpoint(String endpoint) { - String ret = endpoint; - - if (REMAP2HOST != null && !REMAP2HOST.isEmpty()) { - - final String[] split = endpoint.split(":"); - final String port = split[1]; - - ret = REMAP2HOST + ":" + port; - - } - - return ret; + public interface EndorsementSelector { + SDEndorserState endorserSelector(SDChaindcode sdChaindcode); + EndorsementSelector ENDORSEMENT_SELECTION_RANDOM = ServiceDiscovery.ENDORSEMENT_SELECTION_RANDOM; + EndorsementSelector ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT = ServiceDiscovery.ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; } } diff --git a/src/test/fixture/sdkintegration/chaincodeendorsementpolicyAllMembers.yaml b/src/test/fixture/sdkintegration/chaincodeendorsementpolicyAllMembers.yaml new file mode 100644 index 00000000..21be4d80 --- /dev/null +++ b/src/test/fixture/sdkintegration/chaincodeendorsementpolicyAllMembers.yaml @@ -0,0 +1,15 @@ +# +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- + +identities: # list roles to be used in the policy + user1: {"role": {"name": "member", "mspId": "Org1MSP"}} # role member in org with mspid Org1MSP + user2: {"role": {"name": "member", "mspId": "Org2MSP"}} + +policy: # the policy each MUST sign + 2-of: # signed by one of these groups can be -of where is any digit 2-of, 3-of etc.. + - signed-by: "user1" + - signed-by: "user2" \ No newline at end of file diff --git a/src/test/java/org/hyperledger/fabric/sdk/ServiceDiscoveryTest.java b/src/test/java/org/hyperledger/fabric/sdk/ServiceDiscoveryTest.java new file mode 100755 index 00000000..1756bbee --- /dev/null +++ b/src/test/java/org/hyperledger/fabric/sdk/ServiceDiscoveryTest.java @@ -0,0 +1,359 @@ +/* + * + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, 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; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.hyperledger.fabric.sdk.testutils.TestUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hyperledger.fabric.sdk.ServiceDiscovery.ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; +import static org.hyperledger.fabric.sdk.ServiceDiscovery.EndorsementSelector; +import static org.hyperledger.fabric.sdk.ServiceDiscovery.SDChaindcode; +import static org.hyperledger.fabric.sdk.ServiceDiscovery.SDEndorser; +import static org.hyperledger.fabric.sdk.ServiceDiscovery.SDEndorserState; +import static org.hyperledger.fabric.sdk.ServiceDiscovery.SDLayout; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class ServiceDiscoveryTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void simpleOneEach() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdLayout.addGroup("G1", 1, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + sdLayout.addGroup("G2", 1, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(2, sdEndorsers.size()); + assertTrue(sdLayout == sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 1); + + } + + @Test + public void simpleOneTwoEach() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdLayout.addGroup("G1", 1, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(3, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 2); + + } + + @Test + public void simpleTwoTwoEach() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdLayout.addGroup("G1", 2, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(4, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 2); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 2); + + } + + @Test + public void simpleTwoTwoEachExtras() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:82", 20)); + sdLayout.addGroup("G1", 2, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(4, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 2); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 2); + + } + + @Test + public void simpleTwoTwoEachExtrasCommon() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "commonHost:82", 20)); + sdLayout.addGroup("G1", 2, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org1", "commonHost:82", 20)); // << the same + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(3, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "commonHost").size(), 1); + + } + + @Test + public void twoLayoutTwoTwoEachExtrasCommon() throws Exception { + + EndorsementSelector es = ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:83", 20)); + sdLayout.addGroup("G1", 3, sdl); // << 3 needed + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org1", "commonHost:82", 20)); + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + + sdLayout = new SDLayout(); // another layout the above needs 3 + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "l2localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "l2localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "l2commonHost:82", 20)); + sdLayout.addGroup("G1", 2, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "l2otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "l2otherhost:90", 20)); + sdl.add(new MockSDEndorser("org1", "l2commonHost:82", 20)); + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(3, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "l2localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "l2otherhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "l2commonHost").size(), 1); + + } + + @Test + public void simpleOneEachRandom() throws Exception { + + EndorsementSelector es = EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdLayout.addGroup("G1", 1, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + sdLayout.addGroup("G2", 1, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + SDEndorserState sdEndorserState = es.endorserSelector(cc); + for (int i = 64; i > 0; --i) { + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(2, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 1); + } + + } + + @Test + public void simpleOneTwoEachRandom() throws Exception { + + EndorsementSelector es = EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdLayout.addGroup("G1", 1, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:91", 20)); + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + SDChaindcode cc = new SDChaindcode("fakecc", lol); + for (int i = 64; i > 0; --i) { + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + assertEquals(3, sdEndorsers.size()); + assertSame(sdLayout, sdEndorserState.getPickedLayout()); + + assertEquals(filterByEndpoint(sdEndorsers, "localhost").size(), 1); + assertEquals(filterByEndpoint(sdEndorsers, "otherhost").size(), 2); + } + + } + + @Test + public void twoLayoutTwoTwoEachExtrasCommonRandom() throws Exception { + + EndorsementSelector es = EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM; + + List lol = new LinkedList<>(); + SDLayout sdLayout = new SDLayout(); + List sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "localhost:83", 20)); + sdLayout.addGroup("G1", 3, sdl); // << 3 needed + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:90", 20)); + sdl.add(new MockSDEndorser("org2", "otherhost:82", 20)); + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + + sdLayout = new SDLayout(); // another layout the above needs 3 + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org1", "l2localhost:80", 20)); + sdl.add(new MockSDEndorser("org1", "l2localhost:81", 20)); + sdl.add(new MockSDEndorser("org1", "l2localhost:82", 20)); + sdLayout.addGroup("G1", 1, sdl); + sdl = new LinkedList<>(); + sdl.add(new MockSDEndorser("org2", "l2otherhost:93", 20)); + sdl.add(new MockSDEndorser("org2", "l2otherhost:90", 20)); + sdl.add(new MockSDEndorser("org1", "l2otherhost:82", 20)); + + sdLayout.addGroup("G2", 2, sdl); + lol.add(sdLayout); + + SDChaindcode cc = new SDChaindcode("fakecc", lol); + for (int i = 64; i > 0; --i) { + SDEndorserState sdEndorserState = es.endorserSelector(cc); + Collection sdEndorsers = sdEndorserState.getSdEndorsers(); + + assertTrue((filterByEndpoint(sdEndorsers, "localhost").size() == 3 && + filterByEndpoint(sdEndorsers, "otherhost").size() == 2) || ( + filterByEndpoint(sdEndorsers, "l2localhost").size() == 1 && + filterByEndpoint(sdEndorsers, "l2otherhost").size() == 2 + + )); + } + + } + + private static class MockSDEndorser extends SDEndorser { + private MockSDEndorser(String mspid, String endpoint, long ledgerHeight) { + super(); + TestUtils.setField(this, "endPoint", endpoint); + TestUtils.setField(this, "mspid", mspid); + TestUtils.setField(this, "ledgerHeight", ledgerHeight); + } + + } + + private static List filterByEndpoint(Collection sdEndorsers, final String needle) { + List ret = new LinkedList<>(); + sdEndorsers.forEach(sdEndorser -> { + if (sdEndorser.getEndpoint().contains(needle)) { + ret.add(sdEndorser); + } + }); + + return ret; + } + +} diff --git a/src/test/java/org/hyperledger/fabric/sdk/testutils/TestConfig.java b/src/test/java/org/hyperledger/fabric/sdk/testutils/TestConfig.java index 4f115712..d7b6ce06 100644 --- a/src/test/java/org/hyperledger/fabric/sdk/testutils/TestConfig.java +++ b/src/test/java/org/hyperledger/fabric/sdk/testutils/TestConfig.java @@ -86,11 +86,6 @@ public boolean isRunningFabricTLS() { private static final String ORG_HYPERLEDGER_FABRIC_SDKTEST_VERSION = System.getenv("ORG_HYPERLEDGER_FABRIC_SDKTEST_VERSION") == null ? "1.3.0" : System.getenv("ORG_HYPERLEDGER_FABRIC_SDKTEST_VERSION"); - static { - //set to fake out discovery during testing to local docker see Endpoint.java's createEndpoint method. - System.setProperty("org.hyperledger.fabric.sdk.test.endpoint_remap_discovery_host_name", "localhost"); // for testing only remaps all endpoint names. - } - int[] fabricVersion = new int[3]; private TestConfig() { diff --git a/src/test/java/org/hyperledger/fabric/sdk/testutils/TestUtils.java b/src/test/java/org/hyperledger/fabric/sdk/testutils/TestUtils.java index 8ff641fb..f493e7dd 100644 --- a/src/test/java/org/hyperledger/fabric/sdk/testutils/TestUtils.java +++ b/src/test/java/org/hyperledger/fabric/sdk/testutils/TestUtils.java @@ -76,7 +76,7 @@ private TestUtils() { public static Object setField(Object o, String fieldName, Object value) { Object oldVal = null; try { - final Field field = o.getClass().getDeclaredField(fieldName); + final Field field = getFieldInt(o.getClass(), fieldName); field.setAccessible(true); oldVal = field.get(o); field.set(o, value); @@ -100,7 +100,7 @@ public static Object invokeMethod(Object o, String methodName, Object... args) t Method[] methods = o.getClass().getDeclaredMethods(); List reduce = new ArrayList<>(Arrays.asList(methods)); for (Iterator i = reduce.iterator(); i.hasNext(); - ) { + ) { Method m = i.next(); if (!methodName.equals(m.getName())) { i.remove(); diff --git a/src/test/java/org/hyperledger/fabric/sdkintegration/ServiceDiscoveryIT.java b/src/test/java/org/hyperledger/fabric/sdkintegration/ServiceDiscoveryIT.java index b78da987..817596c4 100644 --- a/src/test/java/org/hyperledger/fabric/sdkintegration/ServiceDiscoveryIT.java +++ b/src/test/java/org/hyperledger/fabric/sdkintegration/ServiceDiscoveryIT.java @@ -28,17 +28,18 @@ import org.hyperledger.fabric.sdk.BlockEvent; import org.hyperledger.fabric.sdk.ChaincodeID; import org.hyperledger.fabric.sdk.Channel; -import org.hyperledger.fabric.sdk.EndorsementSelector; import org.hyperledger.fabric.sdk.HFClient; import org.hyperledger.fabric.sdk.Orderer; import org.hyperledger.fabric.sdk.Peer; import org.hyperledger.fabric.sdk.Peer.PeerRole; import org.hyperledger.fabric.sdk.ProposalResponse; +import org.hyperledger.fabric.sdk.ServiceDiscovery; import org.hyperledger.fabric.sdk.TransactionProposalRequest; import org.hyperledger.fabric.sdk.TransactionRequest; import org.hyperledger.fabric.sdk.exception.TransactionEventException; import org.hyperledger.fabric.sdk.security.CryptoSuite; import org.hyperledger.fabric.sdk.testutils.TestConfig; +import org.junit.Ignore; import org.junit.Test; import static java.lang.String.format; @@ -57,6 +58,7 @@ public class ServiceDiscoveryIT { String CHAIN_CODE_NAME = "example_cc_go"; TransactionRequest.Type CHAIN_CODE_LANG = TransactionRequest.Type.GO_LANG; + @Ignore //Hostnames reported by service discovery won't work unless you edit hostfile @Test public void setup() throws Exception { //Persistence is not part of SDK. Sample file store is for demonstration purposes only! @@ -105,7 +107,7 @@ public void setup() throws Exception { foo.initialize(); // initialize the channel. - Set expect = new HashSet<>(Arrays.asList(protocol + "//localhost:7050")); //discovered orderer + Set expect = new HashSet<>(Arrays.asList(protocol + "//orderer.example.com:7050")); //discovered orderer for (Orderer orderer : foo.getOrderers()) { expect.remove(orderer.getUrl()); } @@ -131,7 +133,7 @@ public void setup() throws Exception { //Send proposal request discovering the what endorsers (peers) are needed. Collection transactionPropResp = foo.sendTransactionProposalToEndorsers(transactionProposalRequest, - createDiscoveryOptions().setEndorsementSelector(EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM) + createDiscoveryOptions().setEndorsementSelector(ServiceDiscovery.EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM) .setForceDiscovery(true)); assertFalse(transactionPropResp.isEmpty()); @@ -147,14 +149,14 @@ public void setup() throws Exception { foo.sendTransactionProposalToEndorsers(transactionProposalRequest, createDiscoveryOptions().ignoreEndpoints("blah.blah.blah.com:90", "blah.com:80", // aka peer0.org1.example.com our discovery peer. Lets ignore it in endorsers selection and see if other discovered peer endorses. - "localhost:7051") + "peer0.org1.example.com:7051") // if chaincode makes additional chaincode calls or uses collections you should add them with setServiceDiscoveryChaincodeInterests // .setServiceDiscoveryChaincodeInterests(Channel.ServiceDiscoveryChaincodeCalls.createServiceDiscoveryChaincodeCalls("someOtherChaincodeName").addCollections("collection1", "collection2")) ); assertEquals(transactionPropResp.size(), 1); final ProposalResponse proposalResponse = transactionPropResp.iterator().next(); final Peer peer = proposalResponse.getPeer(); - assertEquals(protocol + "//localhost:7056", peer.getUrl()); // not our discovery peer but the discovered one. + assertEquals(protocol + "//peer1.org1.example.com:7056", peer.getUrl()); // not our discovery peer but the discovered one. String expectedTransactionId = null;