org.hyperledger.fabric.sdk.Channel Maven / Gradle / Ivy
/*
* 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.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import io.grpc.StatusRuntimeException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.protos.common.Common.Block;
import org.hyperledger.fabric.protos.common.Common.BlockMetadata;
import org.hyperledger.fabric.protos.common.Common.ChannelHeader;
import org.hyperledger.fabric.protos.common.Common.Envelope;
import org.hyperledger.fabric.protos.common.Common.Header;
import org.hyperledger.fabric.protos.common.Common.HeaderType;
import org.hyperledger.fabric.protos.common.Common.LastConfig;
import org.hyperledger.fabric.protos.common.Common.Metadata;
import org.hyperledger.fabric.protos.common.Common.Payload;
import org.hyperledger.fabric.protos.common.Common.Status;
import org.hyperledger.fabric.protos.common.Configtx;
import org.hyperledger.fabric.protos.common.Configtx.ConfigEnvelope;
import org.hyperledger.fabric.protos.common.Configtx.ConfigGroup;
import org.hyperledger.fabric.protos.common.Configtx.ConfigSignature;
import org.hyperledger.fabric.protos.common.Configtx.ConfigUpdateEnvelope;
import org.hyperledger.fabric.protos.common.Configtx.ConfigValue;
import org.hyperledger.fabric.protos.common.Ledger;
import org.hyperledger.fabric.protos.discovery.Protocol;
import org.hyperledger.fabric.protos.msp.MspConfig;
import org.hyperledger.fabric.protos.orderer.Ab;
import org.hyperledger.fabric.protos.orderer.Ab.BroadcastResponse;
import org.hyperledger.fabric.protos.orderer.Ab.DeliverResponse;
import org.hyperledger.fabric.protos.orderer.Ab.SeekInfo;
import org.hyperledger.fabric.protos.orderer.Ab.SeekPosition;
import org.hyperledger.fabric.protos.orderer.Ab.SeekSpecified;
import org.hyperledger.fabric.protos.peer.Configuration;
import org.hyperledger.fabric.protos.peer.FabricProposal;
import org.hyperledger.fabric.protos.peer.FabricProposal.SignedProposal;
import org.hyperledger.fabric.protos.peer.FabricProposalResponse;
import org.hyperledger.fabric.protos.peer.FabricProposalResponse.Response;
import org.hyperledger.fabric.protos.peer.FabricTransaction.ProcessedTransaction;
import org.hyperledger.fabric.protos.peer.Query;
import org.hyperledger.fabric.protos.peer.Query.ChaincodeInfo;
import org.hyperledger.fabric.protos.peer.Query.ChaincodeQueryResponse;
import org.hyperledger.fabric.protos.peer.Query.ChannelQueryResponse;
import org.hyperledger.fabric.sdk.BlockEvent.TransactionEvent;
import org.hyperledger.fabric.sdk.Peer.PeerRole;
import org.hyperledger.fabric.sdk.ServiceDiscovery.SDChaindcode;
import org.hyperledger.fabric.sdk.ServiceDiscovery.SDEndorser;
import org.hyperledger.fabric.sdk.ServiceDiscovery.SDEndorserState;
import org.hyperledger.fabric.sdk.ServiceDiscovery.SDNetwork;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.EventHubException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.exception.ServiceDiscoveryException;
import org.hyperledger.fabric.sdk.exception.TransactionEventException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.helper.Config;
import org.hyperledger.fabric.sdk.helper.DiagnosticFileDumper;
import org.hyperledger.fabric.sdk.helper.Utils;
import org.hyperledger.fabric.sdk.security.certgen.TLSCertificateBuilder;
import org.hyperledger.fabric.sdk.security.certgen.TLSCertificateKeyPair;
import org.hyperledger.fabric.sdk.transaction.GetConfigBlockBuilder;
import org.hyperledger.fabric.sdk.transaction.InstallProposalBuilder;
import org.hyperledger.fabric.sdk.transaction.InstantiateProposalBuilder;
import org.hyperledger.fabric.sdk.transaction.JoinPeerProposalBuilder;
import org.hyperledger.fabric.sdk.transaction.ProposalBuilder;
import org.hyperledger.fabric.sdk.transaction.ProtoUtils;
import org.hyperledger.fabric.sdk.transaction.QueryCollectionsConfigBuilder;
import org.hyperledger.fabric.sdk.transaction.QueryInstalledChaincodesBuilder;
import org.hyperledger.fabric.sdk.transaction.QueryInstantiatedChaincodesBuilder;
import org.hyperledger.fabric.sdk.transaction.QueryPeerChannelsBuilder;
import org.hyperledger.fabric.sdk.transaction.TransactionBuilder;
import org.hyperledger.fabric.sdk.transaction.TransactionContext;
import org.hyperledger.fabric.sdk.transaction.UpgradeProposalBuilder;
import static java.lang.String.format;
import static org.hyperledger.fabric.sdk.Channel.PeerOptions.createPeerOptions;
import static org.hyperledger.fabric.sdk.Channel.TransactionOptions.createTransactionOptions;
import static org.hyperledger.fabric.sdk.User.userContextCheck;
import static org.hyperledger.fabric.sdk.helper.Utils.isNullOrEmpty;
import static org.hyperledger.fabric.sdk.helper.Utils.toHexString;
import static org.hyperledger.fabric.sdk.transaction.ProtoUtils.createSeekInfoEnvelope;
import static org.hyperledger.fabric.sdk.transaction.ProtoUtils.getSignatureHeaderAsByteString;
/**
* The class representing a channel with which the client SDK interacts.
*
*/
public class Channel implements Serializable {
private static final long serialVersionUID = -3266164166893832538L;
private static final Config config = Config.getConfig();
private static final Log logger = LogFactory.getLog(Channel.class);
private static final boolean IS_DEBUG_LEVEL = logger.isDebugEnabled();
private static final boolean IS_WARN_LEVEL = logger.isWarnEnabled();
private static final boolean IS_TRACE_LEVEL = logger.isTraceEnabled();
private static final DiagnosticFileDumper diagnosticFileDumper = IS_TRACE_LEVEL
? config.getDiagnosticFileDumper() : null;
private static final String SYSTEM_CHANNEL_NAME = "";
private static final long ORDERER_RETRY_WAIT_TIME = config.getOrdererRetryWaitTime();
private static final long CHANNEL_CONFIG_WAIT_TIME = config.getChannelConfigWaitTime();
private static final Random RANDOM = new Random();
private static final String BLOCK_LISTENER_TAG = "BLOCK_LISTENER_HANDLE";
// final Set eventingPeers = Collections.synchronizedSet(new HashSet<>());
private static final long DELTA_SWEEP = config.getTransactionListenerCleanUpTimeout();
private static final String CHAINCODE_EVENTS_TAG = "CHAINCODE_EVENTS_HANDLE";
final Collection orderers = Collections.synchronizedCollection(new LinkedList<>());
private transient Map ordererEndpointMap = Collections.synchronizedMap(new HashMap<>());
final Collection eventHubs = Collections.synchronizedCollection(new LinkedList<>());
// Name of the channel is only meaningful to the client
private final String name;
private transient String toString;
// The peers on this channel to which the client can connect
private final Collection peers = Collections.synchronizedSet(new HashSet<>());
private final Map peerOptionsMap = Collections.synchronizedMap(new HashMap<>());
private transient Map peerEndpointMap = Collections.synchronizedMap(new HashMap<>());
private final Map> peerRoleSetMap = Collections.synchronizedMap(new HashMap<>());
private transient String chaincodeEventUpgradeListenerHandle;
private transient String transactionListenerProcessorHandle;
private final boolean systemChannel;
private transient LinkedHashMap chainCodeListeners = new LinkedHashMap<>();
transient HFClient client;
private Set discoveryEndpoints = Collections.synchronizedSet(new HashSet<>());
/**
* Runs processing events from event hubs.
*/
transient Thread eventQueueThread = null;
private transient volatile boolean initialized = false;
private transient volatile boolean shutdown = false;
private transient Block genesisBlock;
private transient Map msps = new HashMap<>();
/**
* A queue each eventing hub will write events to.
*/
private transient ChannelEventQue channelEventQue = new ChannelEventQue();
private transient LinkedHashMap blockListeners = new LinkedHashMap<>();
private transient LinkedHashMap> txListeners = new LinkedHashMap<>();
//Cleans up any transaction listeners that will probably never complete.
private transient ScheduledFuture sweeper = null;
private transient String blh = null;
private transient ServiceDiscovery serviceDiscovery;
{
for (Peer.PeerRole peerRole : EnumSet.allOf(PeerRole.class)) {
peerRoleSetMap.put(peerRole, Collections.synchronizedSet(new HashSet<>()));
}
}
// public void clean(){
// channelEventQue = null;
// }
//
private Channel(String name, HFClient hfClient, Orderer orderer, ChannelConfiguration channelConfiguration, byte[][] signers) throws InvalidArgumentException, TransactionException {
this(name, hfClient, false);
logger.debug(format("Creating new channel %s on the Fabric", name));
Channel ordererChannel = orderer.getChannel();
try {
addOrderer(orderer);
//-----------------------------------------
Envelope ccEnvelope = Envelope.parseFrom(channelConfiguration.getChannelConfigurationAsBytes());
final Payload ccPayload = Payload.parseFrom(ccEnvelope.getPayload());
final ChannelHeader ccChannelHeader = ChannelHeader.parseFrom(ccPayload.getHeader().getChannelHeader());
if (ccChannelHeader.getType() != HeaderType.CONFIG_UPDATE.getNumber()) {
throw new InvalidArgumentException(format("Creating channel; %s expected config block type %s, but got: %s",
name,
HeaderType.CONFIG_UPDATE.name(),
HeaderType.forNumber(ccChannelHeader.getType())));
}
if (!name.equals(ccChannelHeader.getChannelId())) {
throw new InvalidArgumentException(format("Expected config block for channel: %s, but got: %s", name,
ccChannelHeader.getChannelId()));
}
final ConfigUpdateEnvelope configUpdateEnv = ConfigUpdateEnvelope.parseFrom(ccPayload.getData());
ByteString configUpdate = configUpdateEnv.getConfigUpdate();
sendUpdateChannel(configUpdate.toByteArray(), signers, orderer);
// final ConfigUpdateEnvelope.Builder configUpdateEnvBuilder = configUpdateEnv.toBuilder();`
//---------------------------------------
// sendUpdateChannel(channelConfiguration, signers, orderer);
getGenesisBlock(orderer); // get Genesis block to make sure channel was created.
if (genesisBlock == null) {
throw new TransactionException(format("New channel %s error. Genesis bock returned null", name));
}
logger.debug(format("Created new channel %s on the Fabric done.", name));
} catch (TransactionException e) {
orderer.unsetChannel();
if (null != ordererChannel) {
orderer.setChannel(ordererChannel);
}
logger.error(format("Channel %s error: %s", name, e.getMessage()), e);
throw e;
} catch (Exception e) {
orderer.unsetChannel();
if (null != ordererChannel) {
orderer.setChannel(ordererChannel);
}
String msg = format("Channel %s error: %s", name, e.getMessage());
logger.error(msg, e);
throw new TransactionException(msg, e);
}
}
Channel(String name, HFClient client) throws InvalidArgumentException {
this(name, client, false);
}
/**
* @param name
* @param client
* @throws InvalidArgumentException
*/
private Channel(String name, HFClient client, final boolean systemChannel) throws InvalidArgumentException {
this.systemChannel = systemChannel;
if (systemChannel) {
name = SYSTEM_CHANNEL_NAME; //It's special !
initialized = true;
} else {
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("Channel name is invalid can not be null or empty.");
}
}
if (null == client) {
throw new InvalidArgumentException("Channel client is invalid can not be null.");
}
this.name = name;
this.client = client;
toString = "Channel{id: " + config.getNextID() + ", name: " + name + "}";
logger.debug(format("Creating channel: %s, client context %s", isSystemChannel() ? "SYSTEM_CHANNEL" : name, client.getUserContext().getName()));
}
/**
* For requests that are not targeted for a specific channel.
* User's can not directly create this channel.
*
* @param client
* @return a new system channel.
* @throws InvalidArgumentException
*/
static Channel newSystemChannel(HFClient client) throws InvalidArgumentException {
return new Channel(SYSTEM_CHANNEL_NAME, client, true);
}
/**
* createNewInstance
*
* @param name
* @return A new channel
*/
static Channel createNewInstance(String name, HFClient clientContext) throws InvalidArgumentException {
return new Channel(name, clientContext);
}
static Channel createNewInstance(String name, HFClient hfClient, Orderer orderer, ChannelConfiguration channelConfiguration, byte[]... signers) throws InvalidArgumentException, TransactionException {
return new Channel(name, hfClient, orderer, channelConfiguration, signers);
}
private static void checkHandle(final String tag, final String handle) throws InvalidArgumentException {
if (isNullOrEmpty(handle)) {
throw new InvalidArgumentException("Handle is invalid.");
}
if (!handle.startsWith(tag) || !handle.endsWith(tag)) {
throw new InvalidArgumentException("Handle is wrong type.");
}
}
@Override
public String toString() {
return toString;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
toString = "Channel{id: " + config.getNextID() + ", name: " + name + "}";
initialized = false;
lastChaincodeUpgradeEventBlock = 0;
shutdown = false;
msps = new HashMap<>();
txListeners = new LinkedHashMap<>();
channelEventQue = new ChannelEventQue();
blockListeners = new LinkedHashMap<>();
peerEndpointMap = Collections.synchronizedMap(new HashMap<>());
setSDPeerAddition(new SDOPeerDefaultAddition(getServiceDiscoveryProperties()));
// sdOrdererAddition = DEFAULT_ORDERER_ADDITION;
endorsementSelector = ServiceDiscovery.DEFAULT_ENDORSEMENT_SELECTION;
chainCodeListeners = new LinkedHashMap<>();
for (Peer peer : peers) {
peerEndpointMap.put(peer.getEndpoint(), peer);
}
ordererEndpointMap = Collections.synchronizedMap(new HashMap<>());
for (Orderer orderer : orderers) {
ordererEndpointMap.put(orderer.getEndpoint(), orderer);
}
for (EventHub eventHub : getEventHubs()) {
eventHub.setEventQue(channelEventQue);
}
}
/**
* Get all Event Hubs on this channel.
*
* @return Event Hubs
*/
public Collection getEventHubs() {
return Collections.unmodifiableCollection(eventHubs);
}
/**
* Update channel with specified channel configuration
*
* @param updateChannelConfiguration Updated Channel configuration
* @param signers signers
* @throws TransactionException
* @throws InvalidArgumentException
*/
public void updateChannelConfiguration(UpdateChannelConfiguration updateChannelConfiguration, byte[]... signers) throws TransactionException, InvalidArgumentException {
updateChannelConfiguration(updateChannelConfiguration, getRandomOrderer(), signers);
}
/**
* Update channel with specified channel configuration
*
* @param updateChannelConfiguration Channel configuration
* @param signers signers
* @param orderer The specific orderer to use.
* @throws TransactionException
* @throws InvalidArgumentException
*/
public void updateChannelConfiguration(UpdateChannelConfiguration updateChannelConfiguration, Orderer orderer, byte[]... signers) throws TransactionException, InvalidArgumentException {
checkChannelState();
checkOrderer(orderer);
try {
final long startLastConfigIndex = getLastConfigIndex(orderer);
logger.trace(format("startLastConfigIndex: %d. Channel config wait time is: %d",
startLastConfigIndex, CHANNEL_CONFIG_WAIT_TIME));
sendUpdateChannel(updateChannelConfiguration.getUpdateChannelConfigurationAsBytes(), signers, orderer);
long currentLastConfigIndex = -1;
final long nanoTimeStart = System.nanoTime();
//Try to wait to see the channel got updated but don't fail if we don't see it.
do {
currentLastConfigIndex = getLastConfigIndex(orderer);
if (currentLastConfigIndex == startLastConfigIndex) {
final long duration = TimeUnit.MILLISECONDS.convert(System.nanoTime() - nanoTimeStart, TimeUnit.NANOSECONDS);
if (duration > CHANNEL_CONFIG_WAIT_TIME) {
logger.warn(format("Channel %s did not get updated last config after %d ms, Config wait time: %d ms. startLastConfigIndex: %d, currentLastConfigIndex: %d ",
name, duration, CHANNEL_CONFIG_WAIT_TIME, startLastConfigIndex, currentLastConfigIndex));
//waited long enough ..
currentLastConfigIndex = startLastConfigIndex - 1L; // just bail don't throw exception.
} else {
try {
Thread.sleep(ORDERER_RETRY_WAIT_TIME); //try again sleep
} catch (InterruptedException e) {
TransactionException te = new TransactionException("update channel thread Sleep", e);
logger.warn(te.getMessage(), te);
}
}
}
logger.trace(format("currentLastConfigIndex: %d", currentLastConfigIndex));
} while (currentLastConfigIndex == startLastConfigIndex);
} catch (TransactionException e) {
logger.error(format("Channel %s error: %s", name, e.getMessage()), e);
throw e;
} catch (Exception e) {
String msg = format("Channel %s error: %s", name, e.getMessage());
logger.error(msg, e);
throw new TransactionException(msg, e);
}
}
private void sendUpdateChannel(byte[] configupdate, byte[][] signers, Orderer orderer) throws TransactionException, InvalidArgumentException {
logger.debug(format("Channel %s sendUpdateChannel", name));
checkOrderer(orderer);
try {
final long nanoTimeStart = System.nanoTime();
int statusCode = 0;
do {
//Make sure we have fresh transaction context for each try just to be safe.
TransactionContext transactionContext = getTransactionContext();
ConfigUpdateEnvelope.Builder configUpdateEnvBuilder = ConfigUpdateEnvelope.newBuilder();
configUpdateEnvBuilder.setConfigUpdate(ByteString.copyFrom(configupdate));
for (byte[] signer : signers) {
configUpdateEnvBuilder.addSignatures(
ConfigSignature.parseFrom(signer));
}
//--------------
// Construct Payload Envelope.
final ByteString sigHeaderByteString = getSignatureHeaderAsByteString(transactionContext);
final ChannelHeader payloadChannelHeader = ProtoUtils.createChannelHeader(HeaderType.CONFIG_UPDATE,
transactionContext.getTxID(), name, transactionContext.getEpoch(), transactionContext.getFabricTimestamp(), null, null);
final Header payloadHeader = Header.newBuilder().setChannelHeader(payloadChannelHeader.toByteString())
.setSignatureHeader(sigHeaderByteString).build();
final ByteString payloadByteString = Payload.newBuilder()
.setHeader(payloadHeader)
.setData(configUpdateEnvBuilder.build().toByteString())
.build().toByteString();
ByteString payloadSignature = transactionContext.signByteStrings(payloadByteString);
Envelope payloadEnv = Envelope.newBuilder()
.setSignature(payloadSignature)
.setPayload(payloadByteString).build();
BroadcastResponse trxResult = orderer.sendTransaction(payloadEnv);
statusCode = trxResult.getStatusValue();
logger.debug(format("Channel %s sendUpdateChannel %d", name, statusCode));
if (statusCode == 404 || statusCode == 503) {
// these we can retry..
final long duration = TimeUnit.MILLISECONDS.convert(System.nanoTime() - nanoTimeStart, TimeUnit.NANOSECONDS);
if (duration > CHANNEL_CONFIG_WAIT_TIME) {
//waited long enough .. throw an exception
String info = trxResult.getInfo();
if (null == info) {
info = "";
}
throw new TransactionException(format("Channel %s update error timed out after %d ms. Status value %d. Status %s. %s", name,
duration, statusCode, trxResult.getStatus().name(), info));
}
try {
Thread.sleep(ORDERER_RETRY_WAIT_TIME); //try again sleep
} catch (InterruptedException e) {
TransactionException te = new TransactionException("update thread Sleep", e);
logger.warn(te.getMessage(), te);
}
} else if (200 != statusCode) {
// Can't retry.
String info = trxResult.getInfo();
if (null == info) {
info = "";
}
throw new TransactionException(format("New channel %s error. StatusValue %d. Status %s. %s", name,
statusCode, "" + trxResult.getStatus(), info));
}
} while (200 != statusCode); // try again
} catch (TransactionException e) {
logger.error(format("Channel %s error: %s", name, e.getMessage()), e);
throw e;
} catch (Exception e) {
String msg = format("Channel %s error: %s", name, e.getMessage());
logger.error(msg, e);
throw new TransactionException(msg, e);
}
}
Enrollment getEnrollment() {
return client.getUserContext().getEnrollment();
}
/**
* Is channel initialized.
*
* @return true if the channel has been initialized.
*/
public boolean isInitialized() {
return initialized;
}
/**
* Get the channel name
*
* @return The name of the channel
*/
public String getName() {
return this.name;
}
/**
* Add a peer to the channel
*
* @param peer The Peer to add.
* @return Channel The current channel added.
* @throws InvalidArgumentException
*/
public Channel addPeer(Peer peer) throws InvalidArgumentException {
return addPeer(peer, createPeerOptions());
}
/**
* Add a peer to the channel
*
* @param peer The Peer to add.
* @param peerOptions see {@link PeerRole}
* @return Channel The current channel added.
* @throws InvalidArgumentException
*/
public Channel addPeer(Peer peer, PeerOptions peerOptions) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (null == peer) {
throw new InvalidArgumentException("Peer is invalid can not be null.");
}
if (peer.getChannel() != null && peer.getChannel() != this) {
throw new InvalidArgumentException(format("Peer already connected to channel %s", peer.getChannel().getName()));
}
if (null == peerOptions) {
throw new InvalidArgumentException("peerOptions is invalid can not be null.");
}
logger.debug(format("%s adding peer: %s, peerOptions: %s", toString(), peer, "" + peerOptions));
peer.setChannel(this);
peers.add(peer);
peerOptionsMap.put(peer, peerOptions.clone());
peerEndpointMap.put(peer.getEndpoint(), peer);
if (peerOptions.getPeerRoles().contains(PeerRole.SERVICE_DISCOVERY)) {
final Properties properties = peer.getProperties();
if ((properties == null) || (isNullOrEmpty(properties.getProperty("clientCertFile")) &&
isNullOrEmpty(properties.getProperty("clientCertBytes")))) {
TLSCertificateBuilder tlsCertificateBuilder = new TLSCertificateBuilder();
TLSCertificateKeyPair tlsCertificateKeyPair = tlsCertificateBuilder.clientCert();
peer.setTLSCertificateKeyPair(tlsCertificateKeyPair);
}
discoveryEndpoints.add(peer.getEndpoint());
}
for (Map.Entry> peerRole : peerRoleSetMap.entrySet()) {
if (peerOptions.getPeerRoles().contains(peerRole.getKey())) {
peerRole.getValue().add(peer);
}
}
if (isInitialized() && peerOptions.getPeerRoles().contains(PeerRole.EVENT_SOURCE)) {
try {
peer.initiateEventing(getTransactionContext(), getPeersOptions(peer));
} catch (TransactionException e) {
logger.error(format("Error channel %s enabling eventing on peer %s", toString(), peer));
}
}
return this;
}
/**
* Join the peer to the channel. The peer is added with all roles see {@link PeerOptions}
*
* @param peer the peer to join the channel.
* @return
* @throws ProposalException
*/
public Channel joinPeer(Peer peer) throws ProposalException {
return joinPeer(peer, createPeerOptions());
}
private Collection getEventingPeers() {
return Collections.unmodifiableCollection(peerRoleSetMap.get(PeerRole.EVENT_SOURCE));
}
private Collection getEndorsingPeers() {
return Collections.unmodifiableCollection(peerRoleSetMap.get(PeerRole.ENDORSING_PEER));
}
private Collection getChaincodePeers() {
return Collections.unmodifiableCollection(getPeers(EnumSet.of(PeerRole.CHAINCODE_QUERY, PeerRole.ENDORSING_PEER)));
}
private Collection getChaincodeQueryPeers() {
return Collections.unmodifiableCollection(peerRoleSetMap.get(PeerRole.CHAINCODE_QUERY));
}
private Collection getLedgerQueryPeers() {
return Collections.unmodifiableCollection(peerRoleSetMap.get(PeerRole.LEDGER_QUERY));
}
private Collection getServiceDiscoveryPeers() {
return Collections.unmodifiableCollection(peerRoleSetMap.get(PeerRole.SERVICE_DISCOVERY));
}
/**
* @param peer the peer to join the channel.
* @param peerOptions see {@link PeerOptions}
* @return
* @throws ProposalException
*/
public Channel joinPeer(Peer peer, PeerOptions peerOptions) throws ProposalException {
try {
return joinPeer(getRandomOrderer(), peer, peerOptions);
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
throw new ProposalException(e);
}
}
/**
* Join peer to channel
*
* @param orderer The orderer to get the genesis block.
* @param peer the peer to join the channel.
* @param peerOptions see {@link PeerOptions}
* @return
* @throws ProposalException
*/
public Channel joinPeer(Orderer orderer, Peer peer, PeerOptions peerOptions) throws ProposalException {
logger.debug(format("Channel %s joining peer %s, url: %s", name, peer.getName(), peer.getUrl()));
if (shutdown) {
throw new ProposalException(format("Channel %s has been shutdown.", name));
}
Channel peerChannel = peer.getChannel();
if (null != peerChannel && peerChannel != this) {
throw new ProposalException(format("Can not add peer %s to channel %s because it already belongs to channel %s.", peer.getName(), name, peerChannel.getName()));
}
logger.info(format("%s joining %s.", toString(), peer));
if (genesisBlock == null && orderers.isEmpty()) {
ProposalException e = new ProposalException("Channel missing genesis block and no orderers configured");
logger.error(e.getMessage(), e);
}
try {
genesisBlock = getGenesisBlock(orderer);
logger.debug(format("Channel %s got genesis block", name));
final Channel systemChannel = newSystemChannel(client); //channel is not really created and this is targeted to system channel
TransactionContext transactionContext = systemChannel.getTransactionContext();
FabricProposal.Proposal joinProposal = JoinPeerProposalBuilder.newBuilder()
.context(transactionContext)
.genesisBlock(genesisBlock)
.build();
logger.debug("Getting signed proposal.");
SignedProposal signedProposal = getSignedProposal(transactionContext, joinProposal);
logger.debug("Got signed proposal.");
addPeer(peer, peerOptions); //need to add peer.
Collection resp = sendProposalToPeers(new ArrayList<>(Collections.singletonList(peer)),
signedProposal, transactionContext);
ProposalResponse pro = resp.iterator().next();
if (pro.getStatus() == ProposalResponse.Status.SUCCESS) {
logger.info(format("Peer %s joined into channel %s", peer, toString()));
} else {
removePeerInternal(peer);
throw new ProposalException(format("Join peer to channel %s failed. Status %s, details: %s",
name, pro.getStatus().toString(), pro.getMessage()));
}
} catch (ProposalException e) {
logger.error(format("%s removing peer %s due to exception %s", toString(), peer, e.getMessage()));
removePeerInternal(peer);
logger.error(e);
throw e;
} catch (Exception e) {
logger.error(format("%s removing peer %s due to exception %s", toString(), peer, e.getMessage()));
peers.remove(peer);
logger.error(e);
throw new ProposalException(e.getMessage(), e);
}
return this;
}
private Block getConfigBlock(List peers) throws ProposalException {
if (shutdown) {
throw new ProposalException(format("Channel %s has been shutdown.", name));
}
if (peers.isEmpty()) {
throw new ProposalException("No peers go get config block");
}
TransactionContext transactionContext = null;
SignedProposal signedProposal = null;
try {
transactionContext = getTransactionContext();
transactionContext.verify(false); // can't verify till we get the config block.
FabricProposal.Proposal proposal = GetConfigBlockBuilder.newBuilder()
.context(transactionContext)
.channelId(name)
.build();
logger.debug("Getting signed proposal.");
signedProposal = getSignedProposal(transactionContext, proposal);
logger.debug("Got signed proposal.");
} catch (Exception e) {
throw new ProposalException(e);
}
ProposalException lastException = new ProposalException(format("getConfigBlock for channel %s failed.", name));
for (Peer peer : peers) {
try {
Collection resp = sendProposalToPeers(new ArrayList<>(Collections.singletonList(peer)),
signedProposal, transactionContext);
if (!resp.isEmpty()) {
ProposalResponse pro = resp.iterator().next();
if (pro.getStatus() == ProposalResponse.Status.SUCCESS) {
logger.trace(format("getConfigBlock from peer %s on channel %s success", peer.getName(), name));
return Block.parseFrom(pro.getProposalResponse().getResponse().getPayload().toByteArray());
} else {
lastException = new ProposalException(format("getConfigBlock for channel %s failed with peer %s. Status %s, details: %s",
name, peer.getName(), pro.getStatus().toString(), pro.getMessage()));
logger.warn(lastException.getMessage());
}
} else {
logger.warn(format("Got empty proposals from %s", peer));
}
} catch (Exception e) {
lastException = new ProposalException(format("getConfigBlock for channel %s failed with peer %s.", name, peer.getName()), e);
logger.warn(lastException.getMessage());
}
}
throw lastException;
}
/**
* Removes the peer connection from the channel.
* This does NOT unjoin the peer from from the channel.
* Fabric does not support that at this time -- maybe some day, but not today
*
* @param peer
*/
public void removePeer(Peer peer) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Can not remove peer from channel %s already shutdown.", name));
}
logger.debug(format("removePeer %s from channel %s", peer, toString()));
checkPeer(peer);
removePeerInternal(peer);
peer.shutdown(true);
}
private void removePeerInternal(Peer peer) {
logger.debug(format("RemovePeerInternal %s from channel %s", peer, toString()));
peers.remove(peer);
peerOptionsMap.remove(peer);
peerEndpointMap.remove(peer.getEndpoint());
for (Set peerRoleSet : peerRoleSetMap.values()) {
peerRoleSet.remove(peer);
}
peer.unsetChannel();
}
/**
* Add an Orderer to this channel.
*
* @param orderer the orderer to add.
* @return this channel.
* @throws InvalidArgumentException
*/
public Channel addOrderer(Orderer orderer) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (null == orderer) {
throw new InvalidArgumentException("Orderer is invalid can not be null.");
}
logger.debug(format("Channel %s adding %s", toString(), orderer.toString()));
orderer.setChannel(this);
ordererEndpointMap.put(orderer.getEndpoint(), orderer);
orderers.add(orderer);
return this;
}
public void removeOrderer(Orderer orderer) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (null == orderer) {
throw new InvalidArgumentException("Orderer is invalid can not be null.");
}
logger.debug(format("Channel %s removing %s", toString(), orderer.toString()));
ordererEndpointMap.remove(orderer.getEndpoint());
orderers.remove(orderer);
orderer.shutdown(true);
}
public PeerOptions getPeersOptions(Peer peer) {
PeerOptions ret = peerOptionsMap.get(peer);
if (ret != null) {
ret = ret.clone();
}
return ret;
}
/**
* Add an Event Hub to this channel.
*
* @param eventHub
* @return this channel
* @throws InvalidArgumentException
*/
public Channel addEventHub(EventHub eventHub) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (null == eventHub) {
throw new InvalidArgumentException("EventHub is invalid can not be null.");
}
logger.debug(format("Channel %s adding event hub %s", toString(), eventHub.toString()));
eventHub.setChannel(this);
eventHub.setEventQue(channelEventQue);
eventHubs.add(eventHub);
if (isInitialized()) {
try {
eventHub.connect(getTransactionContext());
} catch (EventHubException e) {
throw new InvalidArgumentException(e);
}
}
return this;
}
/**
* Get the peers for this channel.
*
* @return the peers.
*/
public Collection getPeers() {
return Collections.unmodifiableCollection(new ArrayList<>(peers));
}
/**
* Get the peers for this channel.
*
* @return the peers.
*/
public Collection getPeers(EnumSet roles) {
Set ret = new HashSet<>(getPeers().size());
for (PeerRole peerRole : roles) {
ret.addAll(peerRoleSetMap.get(peerRole));
}
return Collections.unmodifiableCollection(ret);
}
/**
* Set peerOptions in the channel that has not be initialized yet.
*
* @param peer the peer to set options on.
* @param peerOptions see {@link PeerOptions}
* @return old options.
*/
PeerOptions setPeerOptions(Peer peer, PeerOptions peerOptions) throws InvalidArgumentException {
if (initialized) {
throw new InvalidArgumentException(format("Channel %s already initialized.", name));
}
checkPeer(peer);
PeerOptions ret = getPeersOptions(peer);
removePeerInternal(peer);
addPeer(peer, peerOptions);
return ret;
}
transient volatile long lastChaincodeUpgradeEventBlock = 0;
private synchronized boolean isChaincodeUpgradeEvent(final long blockNumber) {
boolean ret = false;
if (blockNumber > lastChaincodeUpgradeEventBlock) {
lastChaincodeUpgradeEventBlock = blockNumber;
ret = true;
}
return ret;
}
/**
* Initialize the Channel. Starts the channel. event hubs will connect.
*
* @return this channel.
* @throws InvalidArgumentException
* @throws TransactionException
*/
public Channel initialize() throws InvalidArgumentException, TransactionException {
logger.debug(format("Channel %s initialize shutdown %b", name, shutdown));
if (isInitialized()) {
return this;
}
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("Can not initialize channel without a valid name.");
}
if (client == null) {
throw new InvalidArgumentException("Can not initialize channel without a client object.");
}
userContextCheck(client.getUserContext());
if (null == sdOrdererAddition) {
setSDOrdererAddition(new SDOrdererDefaultAddition(getServiceDiscoveryProperties()));
}
if (null == sdPeerAddition) {
setSDPeerAddition(new SDOPeerDefaultAddition(getServiceDiscoveryProperties()));
}
try {
loadCACertificates(false); // put all MSP certs into cryptoSuite if this fails here we'll try again later.
} catch (Exception e) {
logger.warn(format("Channel %s could not load peer CA certificates from any peers.", name));
}
Collection serviceDiscoveryPeers = getServiceDiscoveryPeers();
if (!serviceDiscoveryPeers.isEmpty()) {
logger.trace("Starting service discovery.");
this.serviceDiscovery = new ServiceDiscovery(this, serviceDiscoveryPeers, getTransactionContext());
serviceDiscovery.fullNetworkDiscovery(true);
serviceDiscovery.run();
logger.trace("Completed. service discovery.");
}
try {
logger.debug(format("Eventque started %s", "" + eventQueueThread));
for (EventHub eh : eventHubs) { //Connect all event hubs
eh.connect(getTransactionContext());
}
for (Peer peer : getEventingPeers()) {
peer.initiateEventing(getTransactionContext(), getPeersOptions(peer));
}
logger.debug(format("%d eventhubs initialized", getEventHubs().size()));
transactionListenerProcessorHandle = registerTransactionListenerProcessor(); //Manage transactions.
logger.debug(format("Channel %s registerTransactionListenerProcessor completed", name));
if (serviceDiscovery != null) {
chaincodeEventUpgradeListenerHandle = registerChaincodeEventListener(Pattern.compile("^lscc$"), Pattern.compile("^upgrade$"), (handle, blockEvent, chaincodeEvent) -> {
logger.debug(format("Channel %s got upgrade chaincode event", name));
if (!isShutdown() && isChaincodeUpgradeEvent(blockEvent.getBlockNumber())) {
getExecutorService().execute(() -> serviceDiscovery.fullNetworkDiscovery(true));
}
});
}
startEventQue(); //Run the event for event messages from event hubs.
logger.info(format("Channel %s eventThread started shutdown: %b thread: %s ", toString(), shutdown, eventQueueThread == null ? "null" : eventQueueThread.getName()));
this.initialized = true;
logger.debug(format("Channel %s initialized", name));
return this;
} catch (Exception e) {
TransactionException exp = new TransactionException(e);
logger.error(exp.getMessage(), exp);
throw exp;
}
}
void sdUpdate(SDNetwork sdNetwork) throws InvalidArgumentException, ServiceDiscoveryException {
if (shutdown) {
return;
}
logger.debug(format("Channel %s doing channel update for service discovery.", name));
List remove = new ArrayList<>();
for (Orderer orderer : getOrderers()) {
if (!sdNetwork.getOrdererEndpoints().contains(orderer.getEndpoint())) {
remove.add(orderer);
}
}
remove.forEach(orderer -> {
try {
removeOrderer(orderer);
} catch (InvalidArgumentException e) {
logger.error(e);
}
});
for (ServiceDiscovery.SDOrderer sdOrderer : sdNetwork.getSDOrderers()) {
Orderer orderer = ordererEndpointMap.get(sdOrderer.getEndPoint());
if (shutdown) {
return;
}
if (null == orderer) {
logger.debug(format("Channel %s doing channel update adding new orderer endpoint: %s", name, sdOrderer.getEndPoint()));
sdOrdererAddition.addOrderer(new SDOrdererAdditionInfo() {
@Override
public String getEndpoint() {
return sdOrderer.getEndPoint();
}
@Override
public String getMspId() {
return sdOrderer.getMspid();
}
@Override
public Channel getChannel() {
return Channel.this;
}
@Override
public HFClient getClient() {
return Channel.this.client;
}
@Override
public byte[][] getTLSCerts() {
final Collection tlsCerts = sdOrderer.getTlsCerts();
return tlsCerts.toArray(new byte[tlsCerts.size()][]);
}
@Override
public byte[][] getTLSIntermediateCerts() {
final Collection tlsCerts = sdOrderer.getTlsIntermediateCerts();
return tlsCerts.toArray(new byte[tlsCerts.size()][]);
}
@Override
public Map getEndpointMap() {
return Collections.unmodifiableMap(Channel.this.ordererEndpointMap);
}
});
}
}
remove.clear();
List removePeers = new ArrayList<>();
for (Peer peer : getPeers()) {
if (!sdNetwork.getPeerEndpoints().contains(peer.getEndpoint())) {
if (!discoveryEndpoints.contains(peer.getEndpoint())) { // never remove discovery endpoints.
logger.debug(format("Channel %s doing channel update remove unfound peer endpoint %s ", name, peer.getEndpoint()));
removePeers.add(peer);
}
}
}
removePeers.forEach(peer -> {
try {
removePeer(peer);
} catch (InvalidArgumentException e) {
logger.error(e);
}
});
for (SDEndorser sdEndorser : sdNetwork.getEndorsers()) {
Peer peer = peerEndpointMap.get(sdEndorser.getEndpoint());
if (null == peer) {
if (shutdown) {
return;
}
logger.debug(format("Channel %s doing channel update found new peer endpoint %s", name, sdEndorser.getEndpoint()));
sdPeerAddition.addPeer(new SDPeerAdditionInfo() {
@Override
public String getMspId() {
return sdEndorser.getMspid();
}
@Override
public String getEndpoint() {
return sdEndorser.getEndpoint();
}
@Override
public Channel getChannel() {
return Channel.this;
}
@Override
public HFClient getClient() {
return Channel.this.client;
}
@Override
public byte[][] getTLSCerts() {
final Collection tlsCerts = sdEndorser.getTLSCerts();
return tlsCerts.toArray(new byte[tlsCerts.size()][]);
}
@Override
public byte[][] getTLSIntermediateCerts() {
final Collection tlsCerts = sdEndorser.getTLSIntermediateCerts();
return tlsCerts.toArray(new byte[tlsCerts.size()][]);
}
@Override
public Map getEndpointMap() {
return Collections.unmodifiableMap(Channel.this.peerEndpointMap);
}
});
}
}
}
public Properties getServiceDiscoveryProperties() {
return serviceDiscoveryProperties;
}
public void setServiceDiscoveryProperties(Properties serviceDiscoveryProperties) {
this.serviceDiscoveryProperties = serviceDiscoveryProperties;
}
public interface SDPeerAdditionInfo {
String getMspId();
String getEndpoint();
Channel getChannel();
HFClient getClient();
byte[][] getTLSCerts();
byte[][] getTLSIntermediateCerts();
default byte[] getAllTLSCerts() throws ServiceDiscoveryException {
byte[][] tlsCerts = getTLSCerts();
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
for (byte[] tlsCert : tlsCerts) {
outputStream.write(tlsCert);
}
tlsCerts = getTLSIntermediateCerts();
for (byte[] tlsCert : tlsCerts) {
outputStream.write(tlsCert);
}
return outputStream.toByteArray();
} catch (IOException e) {
throw new ServiceDiscoveryException(e);
}
}
Map getEndpointMap();
}
public interface SDPeerAddition {
Peer addPeer(SDPeerAdditionInfo sdPeerAddition) throws InvalidArgumentException, ServiceDiscoveryException;
}
transient SDPeerAddition sdPeerAddition = null;
/**
* Set service discovery peer addition override.
*
* @param sdOrdererAddition
* @return
*/
public SDOrdererAddition setSDOrdererAddition(SDOrdererAddition sdOrdererAddition) {
SDOrdererAddition ret = this.sdOrdererAddition;
this.sdOrdererAddition = sdOrdererAddition;
return ret;
}
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();
}
}
public interface SDOrdererAdditionInfo {
String getEndpoint();
String getMspId();
Channel getChannel();
HFClient getClient();
byte[][] getTLSCerts();
byte[][] getTLSIntermediateCerts();
default byte[] getAllTLSCerts() throws ServiceDiscoveryException {
byte[][] tlsCerts = getTLSCerts();
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
for (byte[] tlsCert : tlsCerts) {
outputStream.write(tlsCert);
}
tlsCerts = getTLSIntermediateCerts();
for (byte[] tlsCert : tlsCerts) {
outputStream.write(tlsCert);
}
return outputStream.toByteArray();
} catch (IOException e) {
throw new ServiceDiscoveryException(e);
}
}
Map getEndpointMap();
}
public interface SDOrdererAddition {
Orderer addOrderer(SDOrdererAdditionInfo sdOrdererAdditionInfo) throws InvalidArgumentException, ServiceDiscoveryException;
}
private transient SDOrdererAddition sdOrdererAddition = null;
private Properties serviceDiscoveryProperties = new Properties();
private static class SDOrdererDefaultAddition implements SDOrdererAddition {
private final Properties config;
SDOrdererDefaultAddition(Properties config) {
this.config = config == null ? new Properties() : (Properties) config.clone();
}
@Override
public Orderer addOrderer(SDOrdererAdditionInfo sdOrdererAdditionInfo) throws InvalidArgumentException, ServiceDiscoveryException {
Properties properties = new Properties();
final String endpoint = sdOrdererAdditionInfo.getEndpoint();
final String mspid = sdOrdererAdditionInfo.getMspId();
String protocol = findClientProp(config, "protocol", mspid, endpoint, "grpcs:");
String clientCertFile = findClientProp(config, "clientCertFile", mspid, endpoint, null);
if (null != clientCertFile) {
properties.put("clientCertFile", clientCertFile);
}
String clientKeyFile = findClientProp(config, "clientKeyFile", mspid, endpoint, null);
if (null != clientKeyFile) {
properties.put("clientKeyFile", clientKeyFile);
}
String clientCertBytes = findClientProp(config, "clientCertBytes", mspid, endpoint, null);
if (null != clientCertBytes) {
properties.put("clientCertBytes", clientCertBytes);
}
String clientKeyBytes = findClientProp(config, "clientKeyBytes", mspid, endpoint, null);
if (null != clientKeyBytes) {
properties.put("clientKeyBytes", clientKeyBytes);
}
String hostnameOverride = findClientProp(config, "hostnameOverride", mspid, endpoint, null);
if (null != hostnameOverride) {
properties.put("hostnameOverride", hostnameOverride);
}
byte[] pemBytes = sdOrdererAdditionInfo.getAllTLSCerts();
if (pemBytes.length > 0) {
properties.put("pemBytes", pemBytes);
}
Orderer orderer = sdOrdererAdditionInfo.getClient().newOrderer(endpoint,
protocol + "//" + endpoint,
properties);
sdOrdererAdditionInfo.getChannel().addOrderer(orderer);
return orderer;
}
}
private static class SDOPeerDefaultAddition implements SDPeerAddition {
private final Properties config;
SDOPeerDefaultAddition(Properties config) {
this.config = config == null ? new Properties() : (Properties) config.clone();
}
@Override
public Peer addPeer(SDPeerAdditionInfo sdPeerAddition) throws InvalidArgumentException, ServiceDiscoveryException {
Properties properties = new Properties();
final String endpoint = sdPeerAddition.getEndpoint();
final String mspid = sdPeerAddition.getMspId();
String protocol = findClientProp(config, "protocol", mspid, endpoint, "grpcs:");
String clientCertFile = findClientProp(config, "clientCertFile", mspid, endpoint, null);
Peer peer = sdPeerAddition.getEndpointMap().get(endpoint); // maybe there already.
if (null != peer) {
return peer;
}
if (null != clientCertFile) {
properties.put("clientCertFile", clientCertFile);
}
String clientKeyFile = findClientProp(config, "clientKeyFile", mspid, endpoint, null);
if (null != clientKeyFile) {
properties.put("clientKeyFile", clientKeyFile);
}
String clientCertBytes = findClientProp(config, "clientCertBytes", mspid, endpoint, null);
if (null != clientCertBytes) {
properties.put("clientCertBytes", clientCertBytes);
}
String clientKeyBytes = findClientProp(config, "clientKeyBytes", mspid, endpoint, null);
if (null != clientKeyBytes) {
properties.put("clientKeyBytes", clientKeyBytes);
}
String hostnameOverride = findClientProp(config, "hostnameOverride", mspid, endpoint, null);
if (null != hostnameOverride) {
properties.put("hostnameOverride", hostnameOverride);
}
byte[] pemBytes = sdPeerAddition.getAllTLSCerts();
if (pemBytes.length > 0) {
properties.put("pemBytes", pemBytes);
}
peer = sdPeerAddition.getClient().newPeer(endpoint,
protocol + "//" + endpoint,
properties);
sdPeerAddition.getChannel().addPeer(peer, createPeerOptions().setPeerRoles(
EnumSet.of(PeerRole.ENDORSING_PEER, PeerRole.EVENT_SOURCE, PeerRole.LEDGER_QUERY, PeerRole.CHAINCODE_QUERY))); //application can decide on roles.
return peer;
}
}
static String findClientProp(Properties config, final String prop, final String mspid, final String endpoint, String def) {
final String[] split = endpoint.split(":");
final String endpointHost = split[0];
String ret = config.getProperty("org.hyperledger.fabric.sdk.discovery.default." + prop, def);
ret = config.getProperty("org.hyperledger.fabric.sdk.discovery.mspid." + prop + "." + mspid, ret);
ret = config.getProperty("org.hyperledger.fabric.sdk.discovery.endpoint." + prop + "." + endpointHost, ret);
ret = config.getProperty("org.hyperledger.fabric.sdk.discovery.endpoint." + prop + "." + endpoint, ret);
return ret;
}
/**
* Set service discovery peer addition override.
*
* @param sdPeerAddition
* @return
*/
public SDPeerAddition setSDPeerAddition(SDPeerAddition sdPeerAddition) {
SDPeerAddition ret = this.sdPeerAddition;
this.sdPeerAddition = sdPeerAddition;
return ret;
}
/**
* load the peer organizations CA certificates into the channel's trust store so that we
* can verify signatures from peer messages
*
* @throws InvalidArgumentException
* @throws CryptoException
*/
protected synchronized void loadCACertificates(boolean force) throws InvalidArgumentException, CryptoException, TransactionException {
if (!force && msps != null && !msps.isEmpty()) {
return;
}
logger.debug(format("Channel %s loadCACertificates", name));
Map lmsp = parseConfigBlock(force);
if (lmsp == null || lmsp.isEmpty()) {
throw new InvalidArgumentException("Unable to load CA certificates. Channel " + name + " does not have any MSPs.");
}
List certList;
for (MSP msp : lmsp.values()) {
logger.debug("loading certificates for MSP : " + msp.getID());
certList = Arrays.asList(msp.getRootCerts());
if (certList.size() > 0) {
client.getCryptoSuite().loadCACertificatesAsBytes(certList);
}
certList = Arrays.asList(msp.getIntermediateCerts());
if (certList.size() > 0) {
client.getCryptoSuite().loadCACertificatesAsBytes(certList);
}
// not adding admin certs. Admin certs should be signed by the CA
}
logger.debug(format("Channel %s loadCACertificates completed ", name));
}
private Block getGenesisBlock(Orderer orderer) throws TransactionException {
try {
if (genesisBlock != null) {
logger.debug(format("Channel %s getGenesisBlock already present", name));
} else {
final long start = System.currentTimeMillis();
SeekSpecified seekSpecified = SeekSpecified.newBuilder()
.setNumber(0)
.build();
SeekPosition seekPosition = SeekPosition.newBuilder()
.setSpecified(seekSpecified)
.build();
SeekSpecified seekStopSpecified = SeekSpecified.newBuilder()
.setNumber(0)
.build();
SeekPosition seekStopPosition = SeekPosition.newBuilder()
.setSpecified(seekStopSpecified)
.build();
SeekInfo seekInfo = SeekInfo.newBuilder()
.setStart(seekPosition)
.setStop(seekStopPosition)
.setBehavior(SeekInfo.SeekBehavior.BLOCK_UNTIL_READY)
.build();
ArrayList deliverResponses = new ArrayList<>();
seekBlock(seekInfo, deliverResponses, orderer);
DeliverResponse blockresp = deliverResponses.get(1);
Block configBlock = blockresp.getBlock();
if (configBlock == null) {
throw new TransactionException(format("In getGenesisBlock newest block for channel %s fetch bad deliver returned null:", name));
}
int dataCount = configBlock.getData().getDataCount();
if (dataCount < 1) {
throw new TransactionException(format("In getGenesisBlock bad config block data count %d", dataCount));
}
genesisBlock = blockresp.getBlock();
}
} catch (TransactionException e) {
logger.error(e.getMessage(), e);
throw e;
} catch (Exception e) {
TransactionException exp = new TransactionException("getGenesisBlock " + e.getMessage(), e);
logger.error(exp.getMessage(), exp);
throw exp;
}
if (genesisBlock == null) { //make sure it was really set.
TransactionException exp = new TransactionException("getGenesisBlock returned null");
logger.error(exp.getMessage(), exp);
throw exp;
}
logger.debug(format("Channel %s getGenesisBlock done.", name));
return genesisBlock;
}
boolean isSystemChannel() {
return systemChannel;
}
/**
* Is the channel shutdown.
*
* @return return true if the channel is shutdown.
*/
public boolean isShutdown() {
return shutdown;
}
/**
* Get signed byes of the update channel.
*
* @param updateChannelConfiguration
* @param signer
* @return
* @throws InvalidArgumentException
*/
public byte[] getUpdateChannelConfigurationSignature(UpdateChannelConfiguration updateChannelConfiguration, User signer) throws InvalidArgumentException {
userContextCheck(signer);
if (null == updateChannelConfiguration) {
throw new InvalidArgumentException("channelConfiguration is null");
}
try {
TransactionContext transactionContext = getTransactionContext(signer);
final ByteString configUpdate = ByteString.copyFrom(updateChannelConfiguration.getUpdateChannelConfigurationAsBytes());
ByteString sigHeaderByteString = getSignatureHeaderAsByteString(signer, transactionContext);
ByteString signatureByteSting = transactionContext.signByteStrings(new User[] {signer},
sigHeaderByteString, configUpdate)[0];
return ConfigSignature.newBuilder()
.setSignatureHeader(sigHeaderByteString)
.setSignature(signatureByteSting)
.build().toByteArray();
} catch (Exception e) {
throw new InvalidArgumentException(e);
} finally {
logger.debug("finally done");
}
}
ChannelEventQue getChannelEventQue() {
return channelEventQue;
}
ExecutorService getExecutorService() {
return client.getExecutorService();
}
protected Map parseConfigBlock(boolean force) throws TransactionException {
Map lmsps = msps;
if (!force && lmsps != null && !lmsps.isEmpty()) {
return lmsps;
}
try {
Block parseFrom = getConfigBlock(getShuffledPeers());
// final Block configBlock = getConfigurationBlock();
logger.debug(format("Channel %s Got config block getting MSP data and anchorPeers data", name));
Envelope envelope = Envelope.parseFrom(parseFrom.getData().getData(0));
Payload payload = Payload.parseFrom(envelope.getPayload());
ConfigEnvelope configEnvelope = ConfigEnvelope.parseFrom(payload.getData());
ConfigGroup channelGroup = configEnvelope.getConfig().getChannelGroup();
Map newMSPS = traverseConfigGroupsMSP("", channelGroup, new HashMap<>(20));
msps = Collections.unmodifiableMap(newMSPS);
return Collections.unmodifiableMap(newMSPS);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new TransactionException(e);
}
}
private Map traverseConfigGroupsMSP(String name, ConfigGroup configGroup, Map msps) throws InvalidProtocolBufferException {
ConfigValue mspv = configGroup.getValuesMap().get("MSP");
if (null != mspv) {
if (!msps.containsKey(name)) {
MspConfig.MSPConfig mspConfig = MspConfig.MSPConfig.parseFrom(mspv.getValue());
Integer type = mspConfig.getType();
if (type == 0) {
MspConfig.FabricMSPConfig fabricMSPConfig = MspConfig.FabricMSPConfig.parseFrom(mspConfig.getConfig());
msps.put(name, new MSP(name, fabricMSPConfig));
}
}
}
for (Map.Entry gm : configGroup.getGroupsMap().entrySet()) {
traverseConfigGroupsMSP(gm.getKey(), gm.getValue(), msps);
}
return msps;
}
public static class AnchorPeersConfigUpdateResult {
private UpdateChannelConfiguration updateChannelConfiguration = null;
private Collection peersAdded = Collections.emptyList();
private Collection peersRemoved = Collections.emptyList();
private Collection currentPeers = Collections.emptyList();
private Collection updatedPeers = Collections.emptyList();
/**
* The actual config update @see {@link UpdateChannelConfiguration}
*
* @return The config update. May be null when there is an error on no change needs to be done.
*/
public UpdateChannelConfiguration getUpdateChannelConfiguration() {
return updateChannelConfiguration;
}
/**
* The peers to be added.
*
* @return The anchor peers to be added. This is less any that may be already present.
*/
public Collection getPeersAdded() {
return peersAdded;
}
/**
* The peers to be removed..
*
* @return The anchor peers to be removed. This is less any peers not present.
*/
public Collection getPeersRemoved() {
return peersRemoved;
}
/**
* The anchor peers found in the current channel configuration.
*
* @return The anchor peers found in the current channel configuration.
*/
public Collection getCurrentPeers() {
return currentPeers;
}
/**
* The anchor peers found in the updated channel configuration.
*/
public Collection getUpdatedPeers() {
return updatedPeers;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(10000);
sb.append("AnchorPeersConfigUpdateResult:{peersAdded= ");
if (peersAdded == null) {
sb.append("null");
} else {
sb.append(peersAdded.toString());
}
sb.append(", peersRemoved= ");
if (peersRemoved == null) {
sb.append("null");
} else {
sb.append(peersRemoved.toString());
}
sb.append(", currentPeers= ");
if (currentPeers == null) {
sb.append("null");
} else {
sb.append(currentPeers.toString());
}
sb.append(", updatedPeers= ");
if (updatedPeers == null) {
sb.append("null");
} else {
sb.append(updatedPeers.toString());
}
sb.append(", updateChannelConfiguration= ");
if (updateChannelConfiguration == null) {
sb.append("null");
} else {
sb.append(toHexString(updateChannelConfiguration.getUpdateChannelConfigurationAsBytes()));
}
sb.append("}");
return sb.toString();
}
}
/**
* Get a channel configuration update to add or remove peers.
* If both peersToAdd AND peersToRemove are null then only the current anchor peers are reported with @see {@link AnchorPeersConfigUpdateResult#getCurrentPeers()}
*
* @param peer peer to use to the channel configuration from.
* @param userContext The usercontext to use.
* @param peersToAdd Peers to add as Host:Port peer1.org2.com:7022
* @param peersToRemove Peers to remove as Host:Port peer1.org2.com:7022
* @return The AnchorPeersConfigUpdateResult @see {@link AnchorPeersConfigUpdateResult}
* @throws Exception
*/
public AnchorPeersConfigUpdateResult getConfigUpdateAnchorPeers(Peer peer, User userContext, Collection peersToAdd, Collection peersToRemove) throws Exception {
User.userContextCheck(userContext);
checkPeer(peer);
checkChannelState();
final boolean reportOnly = peersToAdd == null && peersToRemove == null;
if (!reportOnly && ((peersToAdd == null || peersToAdd.isEmpty()) && (peersToRemove == null || peersToRemove.isEmpty()))) {
throw new InvalidArgumentException("No anchor peers to add or remove!");
}
if (IS_TRACE_LEVEL) {
StringBuilder sbp = new StringBuilder("null");
String sep = "";
if (peersToAdd != null) {
sbp = new StringBuilder("[");
for (String s : peersToAdd) {
sbp.append(sep).append("'").append(s).append("'");
sep = ", ";
}
sbp.append("]");
}
StringBuilder sbr = new StringBuilder("null");
sep = "";
if (peersToRemove != null) {
sbr = new StringBuilder("[");
for (String s : peersToRemove) {
sbr.append(sep).append("'").append(s).append("'");
sep = ", ";
}
sbr.append("]");
}
logger.trace(format("getConfigUpdateAnchorPeers channel %s, peer: %s, user: %s, peers to add: %s, peers to remove: %s",
name, peer.toString(), userContext.getMspId() + ":" + userContext.getName(),
sbp.toString(), sbr.toString()
));
}
Set peersToAddHS = new HashSet<>(16);
if (null != peersToAdd) {
for (String s : peersToAdd) {
String[] ep = parseEndpoint(s);
peersToAddHS.add(ep[0] + ":" + ep[1]);
}
// peersToAddHS.addAll(peersToAdd);
}
Set peersToRemoveHS = new HashSet<>(16);
if (null != peersToRemove && !peersToRemove.isEmpty()) {
for (String s : peersToRemove) {
String[] ep = parseEndpoint(s);
peersToRemoveHS.add(ep[0] + ":" + ep[1]);
}
peersToRemoveHS.removeAll(peersToAddHS); //add overrides remove;
}
Set peersRemoved = new HashSet<>(peersToAddHS.size());
Set peersAdded = new HashSet<>(peersToRemoveHS.size());
Block configBlock = getConfigBlock(Collections.singletonList(peer));
if (IS_TRACE_LEVEL) {
logger.trace(format("getConfigUpdateAnchorPeers configBlock: %s",
toHexString(configBlock.toByteArray())));
}
Envelope envelope = Envelope.parseFrom(configBlock.getData().getData(0));
Payload payload = Payload.parseFrom(envelope.getPayload());
Header header = payload.getHeader();
ChannelHeader channelHeader = ChannelHeader.parseFrom(header.getChannelHeader());
if (!Objects.equals(name, channelHeader.getChannelId())) {
throw new InvalidArgumentException(format("Expected config block for channel: %s, but got: %s", name, channelHeader.getChannelId()));
}
ConfigEnvelope configEnvelope = ConfigEnvelope.parseFrom(payload.getData());
// ConfigGroup channelGroup = configEnvelope.getConfig().getChannelGroup();
Configtx.Config config = configEnvelope.getConfig();
Configtx.Config.Builder configBuilderUpdate = config.toBuilder();
ConfigGroup.Builder channelGroupBuild = configBuilderUpdate.getChannelGroup().toBuilder();
Map groupsMap = channelGroupBuild.getGroupsMap();
ConfigGroup.Builder application = groupsMap.get("Application").toBuilder();
final String mspid = userContext.getMspId();
ConfigGroup peerOrgConfigGroup = application.getGroupsMap().get(mspid);
if (null == peerOrgConfigGroup) {
StringBuilder sb = new StringBuilder(1000);
String sep = "";
for (String amspid : application.getGroupsMap().keySet()) {
sb.append(sep).append(amspid);
sep = ", ";
}
throw new InvalidArgumentException(format("Expected to find organization matching user context's mspid: %s, but only found %s.", mspid, sb.toString()));
}
ConfigGroup.Builder peerOrgConfigGroupBuilder = peerOrgConfigGroup.toBuilder();
String modPolicy = peerOrgConfigGroup.getModPolicy() != null ? peerOrgConfigGroup.getModPolicy() : "Admins";
Map valuesMap = peerOrgConfigGroupBuilder.getValuesMap();
ConfigValue anchorPeersCV = valuesMap.get("AnchorPeers");
final Set currentAP = new HashSet<>(36); // The anchor peers that exist already.
if (null != anchorPeersCV && anchorPeersCV.getValue() != null) {
modPolicy = anchorPeersCV.getModPolicy() != null ? "Admins" : modPolicy;
Configuration.AnchorPeers anchorPeers = Configuration.AnchorPeers.parseFrom(anchorPeersCV.getValue());
List anchorPeersList = anchorPeers.getAnchorPeersList();
if (anchorPeersList != null) {
for (Configuration.AnchorPeer anchorPeer : anchorPeersList) {
currentAP.add(anchorPeer.getHost().toLowerCase() + ":" + anchorPeer.getPort());
}
}
}
if (IS_TRACE_LEVEL) {
StringBuilder sbp = new StringBuilder("[");
String sep = "";
for (String s : currentAP) {
sbp.append(sep).append("'").append(s).append("'");
sep = ", ";
}
sbp.append("]");
logger.trace(format("getConfigUpdateAnchorPeers channel %s, current anchor peers: %s",
name, sbp.toString()));
}
if (reportOnly) {
logger.trace("getConfigUpdateAnchorPeers reportOnly");
AnchorPeersConfigUpdateResult ret = new AnchorPeersConfigUpdateResult();
ret.currentPeers = currentAP;
ret.peersAdded = Collections.emptyList();
ret.peersRemoved = Collections.emptyList();
ret.updatedPeers = Collections.emptyList();
if (IS_TRACE_LEVEL) {
logger.trace(format("getConfigUpdateAnchorPeers returned: %s",
ret.toString()));
}
return ret;
}
Set peersFinalHS = new HashSet<>(16);
Configuration.AnchorPeers.Builder anchorPeers = Configuration.AnchorPeers.newBuilder();
for (String s : currentAP) {
if (peersToRemoveHS.contains(s)) {
peersRemoved.add(s);
continue;
}
if (!peersToAddHS.contains(s)) {
String[] split = s.split(":");
anchorPeers.addAnchorPeers(Configuration.AnchorPeer.newBuilder().setHost(split[0]).setPort(new Integer(split[1])).build());
peersFinalHS.add(s);
}
}
for (String s : peersToAddHS) {
if (!currentAP.contains(s)) {
peersAdded.add(s);
String[] split = s.split(":");
anchorPeers.addAnchorPeers(Configuration.AnchorPeer.newBuilder().setHost(split[0]).setPort(new Integer(split[1])).build());
peersFinalHS.add(s);
}
}
if (peersRemoved.isEmpty() && peersAdded.isEmpty()) {
logger.trace("getConfigUpdateAnchorPeers no Peers need adding or removing.");
AnchorPeersConfigUpdateResult ret = new AnchorPeersConfigUpdateResult();
ret.currentPeers = currentAP;
ret.peersAdded = Collections.emptyList();
ret.peersRemoved = Collections.emptyList();
ret.updatedPeers = Collections.emptyList();
if (IS_TRACE_LEVEL) {
logger.trace(format("getConfigUpdateAnchorPeers returned: %s",
ret.toString()));
}
return ret;
}
Map m = new HashMap(valuesMap);
m.remove("AnchorPeers");
// org1MSP.clearValues();
// if (!peersFinalHS.isEmpty()) { // if there are anchor peers to add... LEAVE IT.
m.put("AnchorPeers", ConfigValue.newBuilder().setValue(anchorPeers.build().toByteString()).setModPolicy(modPolicy).build());
// }
ConfigGroup build = peerOrgConfigGroupBuilder.putAllValues(m).build();
m.clear();
m.putAll(application.getGroupsMap());
m.put(mspid, build);
// application.putAllValues(m);
application.putAllGroups(m);
ConfigGroup applicationBuilt = application.build();
m.clear();
m.putAll(channelGroupBuild.getGroupsMap());
m.put("Application", applicationBuilt);
channelGroupBuild.putAllGroups(m);
configBuilderUpdate.setChannelGroup(channelGroupBuild.build());
Configtx.ConfigUpdate.Builder updateBlockBuilder = Configtx.ConfigUpdate.newBuilder();
Configtx.Config updated = configBuilderUpdate.build();
if (IS_TRACE_LEVEL) {
logger.trace(format("getConfigUpdateAnchorPeers updated configBlock: %s",
toHexString(updated.toByteArray())));
}
ProtoUtils.computeUpdate(name, config, updated, updateBlockBuilder);
AnchorPeersConfigUpdateResult ret = new AnchorPeersConfigUpdateResult();
ret.currentPeers = currentAP;
ret.peersAdded = peersAdded;
ret.peersRemoved = peersRemoved;
ret.updatedPeers = peersFinalHS;
ret.updateChannelConfiguration = new UpdateChannelConfiguration(updateBlockBuilder.build().toByteArray());
if (IS_TRACE_LEVEL) {
logger.trace(format("getConfigUpdateAnchorPeers returned: %s",
ret.toString()));
}
return ret;
}
/**
* Provide the Channel's latest raw Configuration Block.
*
* @return Channel configuration block.
* @throws TransactionException
*/
private Block getConfigurationBlock() throws TransactionException {
logger.debug(format("getConfigurationBlock for channel %s", name));
try {
Orderer orderer = getRandomOrderer();
long lastConfigIndex = getLastConfigIndex(orderer);
logger.debug(format("Last config index is %d", lastConfigIndex));
Block configBlock = getBlockByNumber(lastConfigIndex);
//Little extra parsing but make sure this really is a config block for this channel.
Envelope envelopeRet = Envelope.parseFrom(configBlock.getData().getData(0));
Payload payload = Payload.parseFrom(envelopeRet.getPayload());
ChannelHeader channelHeader = ChannelHeader.parseFrom(payload.getHeader().getChannelHeader());
if (channelHeader.getType() != HeaderType.CONFIG.getNumber()) {
throw new TransactionException(format("Bad last configuration block type %d, expected %d",
channelHeader.getType(), HeaderType.CONFIG.getNumber()));
}
if (!name.equals(channelHeader.getChannelId())) {
throw new TransactionException(format("Bad last configuration block channel id %s, expected %s",
channelHeader.getChannelId(), name));
}
if (null != diagnosticFileDumper) {
logger.trace(format("Channel %s getConfigurationBlock returned %s", name,
diagnosticFileDumper.createDiagnosticFile(String.valueOf(configBlock).getBytes())));
}
if (!logger.isTraceEnabled()) {
logger.debug(format("Channel %s getConfigurationBlock returned", name));
}
return configBlock;
} catch (TransactionException e) {
logger.error(e.getMessage(), e);
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new TransactionException(e);
}
}
private String[] parseEndpoint(String endPoint) throws InvalidArgumentException {
if (Utils.isNullOrEmpty(endPoint)) {
throw new InvalidArgumentException("Endpoint is null or empty string");
}
try {
URI uri = new URI("grpc://" + endPoint.toLowerCase());
String host = uri.getHost();
if (null == host) {
throw new InvalidArgumentException(format("Endpoint '%s' expected to be format \"host:port\". Hostname part missing", endPoint));
}
int port = uri.getPort();
if (port == -1) {
throw new InvalidArgumentException(format("Endpoint '%s' expected to be format \"host:port\". Port does not seem to be a valid port number. ", endPoint));
}
// int port = Integer.parseInt(split[1]);
if (port < 1) {
throw new InvalidArgumentException(format("Endpoint '%s' expected to be format \"host:port\". Port does not seem to be a valid port number. ", endPoint));
} else if (port > 65535) {
throw new InvalidArgumentException(format("Endpoint '%s' expected to be format \"host:port\". Port does not seem to be a valid port number less than 65535. ", endPoint));
}
return new String[] {host, port + ""};
} catch (URISyntaxException e) {
throw new InvalidArgumentException(format("Endpoint '%s' expected to be format \"host:port\".", endPoint), e);
}
}
/**
* Channel Configuration bytes. Bytes that can be used with configtxlator tool to upgrade the channel.
* Convert to Json for editing with:
* {@code
*
* curl -v POST --data-binary @fooConfig http://host/protolator/decode/common.Config
*
* }
* See http://hyperledger-fabric.readthedocs.io/en/latest/configtxlator.html
*
* @return Channel configuration bytes.
* @throws TransactionException
*/
public byte[] getChannelConfigurationBytes() throws TransactionException {
try {
final Block configBlock = getConfigBlock(getShuffledPeers());
Envelope envelopeRet = Envelope.parseFrom(configBlock.getData().getData(0));
Payload payload = Payload.parseFrom(envelopeRet.getPayload());
ConfigEnvelope configEnvelope = ConfigEnvelope.parseFrom(payload.getData());
return configEnvelope.getConfig().toByteArray();
} catch (Exception e) {
throw new TransactionException(e);
}
}
private long getLastConfigIndex(Orderer orderer) throws TransactionException, InvalidProtocolBufferException {
Block latestBlock = getLatestBlock(orderer);
BlockMetadata blockMetadata = latestBlock.getMetadata();
Metadata metaData = Metadata.parseFrom(blockMetadata.getMetadata(1));
LastConfig lastConfig = LastConfig.parseFrom(metaData.getValue());
return lastConfig.getIndex();
}
private Block getBlockByNumber(final long number) throws TransactionException {
logger.trace(format("getConfigurationBlock for channel %s", name));
try {
logger.trace(format("Last config index is %d", number));
SeekSpecified seekSpecified = SeekSpecified.newBuilder().setNumber(number).build();
SeekPosition seekPosition = SeekPosition.newBuilder()
.setSpecified(seekSpecified)
.build();
SeekInfo seekInfo = SeekInfo.newBuilder()
.setStart(seekPosition)
.setStop(seekPosition)
.setBehavior(SeekInfo.SeekBehavior.BLOCK_UNTIL_READY)
.build();
ArrayList deliverResponses = new ArrayList<>();
seekBlock(seekInfo, deliverResponses, getRandomOrderer());
DeliverResponse blockresp = deliverResponses.get(1);
Block retBlock = blockresp.getBlock();
if (retBlock == null) {
throw new TransactionException(format("newest block for channel %s fetch bad deliver returned null:", name));
}
int dataCount = retBlock.getData().getDataCount();
if (dataCount < 1) {
throw new TransactionException(format("Bad config block data count %d", dataCount));
}
logger.trace(format("Received block for channel %s, block no:%d, transaction count: %d",
name, retBlock.getHeader().getNumber(), retBlock.getData().getDataCount()));
return retBlock;
} catch (TransactionException e) {
logger.error(e.getMessage(), e);
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new TransactionException(e);
}
}
private int seekBlock(SeekInfo seekInfo, List deliverResponses, Orderer ordererIn) throws TransactionException {
logger.trace(format("seekBlock for channel %s", name));
final long start = System.currentTimeMillis();
@SuppressWarnings ("UnusedAssignment")
int statusRC = 404;
try {
do {
statusRC = 404;
final Orderer orderer = ordererIn != null ? ordererIn : getRandomOrderer();
TransactionContext txContext = getTransactionContext();
DeliverResponse[] deliver = orderer.sendDeliver(createSeekInfoEnvelope(txContext, seekInfo, orderer.getClientTLSCertificateDigest()));
if (deliver.length < 1) {
logger.warn(format("Genesis block for channel %s fetch bad deliver missing status block only got blocks:%d", name, deliver.length));
//odd so lets try again....
statusRC = 404;
} else {
DeliverResponse status = deliver[0];
statusRC = status.getStatusValue();
if (statusRC == 404 || statusRC == 503) { //404 - block not found. 503 - service not available usually means kafka is not ready but starting.
logger.warn(format("Bad deliver expected status 200 got %d, Channel %s", status.getStatusValue(), name));
// keep trying... else
statusRC = 404;
} else if (statusRC != 200) { // Assume for anything other than 200 we have a non retryable situation
throw new TransactionException(format("Bad newest block expected status 200 got %d, Channel %s", status.getStatusValue(), name));
} else {
if (deliver.length < 2) {
throw new TransactionException(format("Newest block for channel %s fetch bad deliver missing genesis block only got %d:", name, deliver.length));
} else {
deliverResponses.addAll(Arrays.asList(deliver));
}
}
}
// Not 200 so sleep to try again
if (200 != statusRC) {
long duration = System.currentTimeMillis() - start;
if (duration > config.getGenesisBlockWaitTime()) {
throw new TransactionException(format("Getting block time exceeded %s seconds for channel %s", Long.toString(TimeUnit.MILLISECONDS.toSeconds(duration)), name));
}
try {
Thread.sleep(ORDERER_RETRY_WAIT_TIME); //try again
} catch (InterruptedException e) {
TransactionException te = new TransactionException("seekBlock thread Sleep", e);
logger.warn(te.getMessage(), te);
}
}
} while (statusRC != 200);
} catch (TransactionException e) {
logger.error(e.getMessage(), e);
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new TransactionException(e);
}
return statusRC;
}
private Block getLatestBlock(Orderer orderer) throws TransactionException {
logger.debug(format("getConfigurationBlock for channel %s", name));
SeekPosition seekPosition = SeekPosition.newBuilder()
.setNewest(Ab.SeekNewest.getDefaultInstance())
.build();
SeekInfo seekInfo = SeekInfo.newBuilder()
.setStart(seekPosition)
.setStop(seekPosition)
.setBehavior(SeekInfo.SeekBehavior.BLOCK_UNTIL_READY)
.build();
ArrayList deliverResponses = new ArrayList<>();
seekBlock(seekInfo, deliverResponses, orderer);
DeliverResponse blockresp = deliverResponses.get(1);
Block latestBlock = blockresp.getBlock();
if (latestBlock == null) {
throw new TransactionException(format("newest block for channel %s fetch bad deliver returned null:", name));
}
logger.trace(format("Received latest block for channel %s, block no:%d", name, latestBlock.getHeader().getNumber()));
return latestBlock;
}
public Collection getOrderers() {
return Collections.unmodifiableCollection(new ArrayList<>(orderers));
}
/**
* Send instantiate request to the channel. Chaincode is created and initialized.
*
* @param instantiateProposalRequest send instantiate chaincode proposal request.
* @return Collections of proposal responses
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection sendInstantiationProposal(InstantiateProposalRequest instantiateProposalRequest) throws InvalidArgumentException, ProposalException {
return sendInstantiationProposal(instantiateProposalRequest, getChaincodePeers());
}
/**
* Send instantiate request to the channel. Chaincode is created and initialized.
*
* @param instantiateProposalRequest
* @param peers
* @return responses from peers.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection sendInstantiationProposal(InstantiateProposalRequest instantiateProposalRequest,
Collection peers) throws InvalidArgumentException, ProposalException {
checkChannelState();
if (null == instantiateProposalRequest) {
throw new InvalidArgumentException("InstantiateProposalRequest is null");
}
instantiateProposalRequest.setSubmitted();
checkPeers(peers);
try {
TransactionContext transactionContext = getTransactionContext(instantiateProposalRequest.getUserContext());
transactionContext.setProposalWaitTime(instantiateProposalRequest.getProposalWaitTime());
InstantiateProposalBuilder instantiateProposalbuilder = InstantiateProposalBuilder.newBuilder();
instantiateProposalbuilder.context(transactionContext);
instantiateProposalbuilder.argss(instantiateProposalRequest.getArgs());
instantiateProposalbuilder.chaincodeName(instantiateProposalRequest.getChaincodeName());
instantiateProposalbuilder.chaincodeType(instantiateProposalRequest.getChaincodeLanguage());
instantiateProposalbuilder.chaincodePath(instantiateProposalRequest.getChaincodePath());
instantiateProposalbuilder.chaincodeVersion(instantiateProposalRequest.getChaincodeVersion());
instantiateProposalbuilder.chaincodEndorsementPolicy(instantiateProposalRequest.getChaincodeEndorsementPolicy());
instantiateProposalbuilder.chaincodeCollectionConfiguration(instantiateProposalRequest.getChaincodeCollectionConfiguration());
instantiateProposalbuilder.setTransientMap(instantiateProposalRequest.getTransientMap());
FabricProposal.Proposal instantiateProposal = instantiateProposalbuilder.build();
SignedProposal signedProposal = getSignedProposal(transactionContext, instantiateProposal);
return sendProposalToPeers(peers, signedProposal, transactionContext);
} catch (Exception e) {
throw new ProposalException(e);
}
}
private TransactionContext getTransactionContext() throws InvalidArgumentException {
return getTransactionContext(client.getUserContext());
}
private TransactionContext getTransactionContext(User userContext) throws InvalidArgumentException {
userContext = userContext != null ? userContext : client.getUserContext();
userContextCheck(userContext);
return new TransactionContext(this, userContext, client.getCryptoSuite());
}
/**
* Send install chaincode request proposal to all the channels on the peer.
*
* @param installProposalRequest
* @return
* @throws ProposalException
* @throws InvalidArgumentException
*/
Collection sendInstallProposal(InstallProposalRequest installProposalRequest)
throws ProposalException, InvalidArgumentException {
return sendInstallProposal(installProposalRequest, getChaincodePeers());
}
/**
* Send install chaincode request proposal to the channel.
*
* @param installProposalRequest
* @param peers
* @return
* @throws ProposalException
* @throws InvalidArgumentException
*/
Collection sendInstallProposal(InstallProposalRequest installProposalRequest, Collection peers)
throws ProposalException, InvalidArgumentException {
checkChannelState();
checkPeers(peers);
if (null == installProposalRequest) {
throw new InvalidArgumentException("InstallProposalRequest is null");
}
try {
TransactionContext transactionContext = getTransactionContext(installProposalRequest.getUserContext());
transactionContext.verify(false); // Install will have no signing cause it's not really targeted to a channel.
transactionContext.setProposalWaitTime(installProposalRequest.getProposalWaitTime());
InstallProposalBuilder installProposalbuilder = InstallProposalBuilder.newBuilder();
installProposalbuilder.context(transactionContext);
installProposalbuilder.setChaincodeLanguage(installProposalRequest.getChaincodeLanguage());
installProposalbuilder.chaincodeName(installProposalRequest.getChaincodeName());
installProposalbuilder.chaincodePath(installProposalRequest.getChaincodePath());
installProposalbuilder.chaincodeVersion(installProposalRequest.getChaincodeVersion());
installProposalbuilder.setChaincodeSource(installProposalRequest.getChaincodeSourceLocation());
installProposalbuilder.setChaincodeInputStream(installProposalRequest.getChaincodeInputStream());
installProposalbuilder.setChaincodeMetaInfLocation(installProposalRequest.getChaincodeMetaInfLocation());
FabricProposal.Proposal deploymentProposal = installProposalbuilder.build();
SignedProposal signedProposal = getSignedProposal(transactionContext, deploymentProposal);
return sendProposalToPeers(peers, signedProposal, transactionContext);
} catch (Exception e) {
throw new ProposalException(e);
}
}
/**
* Send Upgrade proposal proposal to upgrade chaincode to a new version.
*
* @param upgradeProposalRequest
* @return Collection of proposal responses.
* @throws ProposalException
* @throws InvalidArgumentException
*/
public Collection sendUpgradeProposal(UpgradeProposalRequest upgradeProposalRequest) throws ProposalException, InvalidArgumentException {
return sendUpgradeProposal(upgradeProposalRequest, getChaincodePeers());
}
/**
* Send Upgrade proposal proposal to upgrade chaincode to a new version.
*
* @param upgradeProposalRequest
* @param peers the specific peers to send to.
* @return Collection of proposal responses.
* @throws ProposalException
* @throws InvalidArgumentException
*/
public Collection sendUpgradeProposal(UpgradeProposalRequest upgradeProposalRequest, Collection peers)
throws InvalidArgumentException, ProposalException {
checkChannelState();
checkPeers(peers);
if (null == upgradeProposalRequest) {
throw new InvalidArgumentException("Upgradeproposal is null");
}
try {
TransactionContext transactionContext = getTransactionContext(upgradeProposalRequest.getUserContext());
//transactionContext.verify(false); // Install will have no signing cause it's not really targeted to a channel.
transactionContext.setProposalWaitTime(upgradeProposalRequest.getProposalWaitTime());
UpgradeProposalBuilder upgradeProposalBuilder = UpgradeProposalBuilder.newBuilder();
upgradeProposalBuilder.context(transactionContext);
upgradeProposalBuilder.argss(upgradeProposalRequest.getArgs());
upgradeProposalBuilder.chaincodeName(upgradeProposalRequest.getChaincodeName());
upgradeProposalBuilder.chaincodePath(upgradeProposalRequest.getChaincodePath());
upgradeProposalBuilder.chaincodeVersion(upgradeProposalRequest.getChaincodeVersion());
upgradeProposalBuilder.chaincodEndorsementPolicy(upgradeProposalRequest.getChaincodeEndorsementPolicy());
upgradeProposalBuilder.chaincodeCollectionConfiguration(upgradeProposalRequest.getChaincodeCollectionConfiguration());
SignedProposal signedProposal = getSignedProposal(transactionContext, upgradeProposalBuilder.build());
return sendProposalToPeers(peers, signedProposal, transactionContext);
} catch (Exception e) {
throw new ProposalException(e);
}
}
private SignedProposal getSignedProposal(TransactionContext transactionContext, FabricProposal.Proposal proposal) throws CryptoException, InvalidArgumentException {
SignedProposal sp;
sp = SignedProposal.newBuilder()
.setProposalBytes(proposal.toByteString())
.setSignature(transactionContext.signByteString(proposal.toByteArray()))
.build();
return sp;
}
private void checkChannelState() throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (!initialized) {
throw new InvalidArgumentException(format("Channel %s has not been initialized.", name));
}
userContextCheck(client.getUserContext());
}
/**
* query this channel for a Block by the block hash.
* The request is retried on each peer on the channel till successful.
*
* This method may not be thread safe if client context is changed!
*
* @param blockHash the hash of the Block in the chain
* @return the {@link BlockInfo} with the given block Hash
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByHash(byte[] blockHash) throws InvalidArgumentException, ProposalException {
return queryBlockByHash(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), blockHash);
}
/**
* query this channel for a Block by the block hash.
* The request is tried on multiple peers.
*
* @param blockHash the hash of the Block in the chain
* @param userContext the user context.
* @return the {@link BlockInfo} with the given block Hash
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByHash(byte[] blockHash, User userContext) throws InvalidArgumentException, ProposalException {
return queryBlockByHash(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), blockHash, userContext);
}
/**
* Query a peer in this channel for a Block by the block hash.
*
* This method may not be thread safe if client context is changed!
*
* @param peer the Peer to query.
* @param blockHash the hash of the Block in the chain.
* @return the {@link BlockInfo} with the given block Hash
* @throws InvalidArgumentException if the channel is shutdown or any of the arguments are not valid.
* @throws ProposalException if an error occurred processing the query.
*/
public BlockInfo queryBlockByHash(Peer peer, byte[] blockHash) throws InvalidArgumentException, ProposalException {
return queryBlockByHash(Collections.singleton(peer), blockHash);
}
/**
* Query a peer in this channel for a Block by the block hash.
* Each peer is tried until successful response.
*
* This method may not be thread safe if client context is changed!
*
* @param peers the Peers to query.
* @param blockHash the hash of the Block in the chain.
* @return the {@link BlockInfo} with the given block Hash
* @throws InvalidArgumentException if the channel is shutdown or any of the arguments are not valid.
* @throws ProposalException if an error occurred processing the query.
*/
public BlockInfo queryBlockByHash(Collection peers, byte[] blockHash) throws InvalidArgumentException, ProposalException {
return queryBlockByHash(peers, blockHash, client.getUserContext());
}
/**
* Query a peer in this channel for a Block by the block hash.
*
* @param peers the Peers to query.
* @param blockHash the hash of the Block in the chain.
* @param userContext the user context
* @return the {@link BlockInfo} with the given block Hash
* @throws InvalidArgumentException if the channel is shutdown or any of the arguments are not valid.
* @throws ProposalException if an error occurred processing the query.
*/
public BlockInfo queryBlockByHash(Collection peers, byte[] blockHash, User userContext) throws InvalidArgumentException, ProposalException {
checkChannelState();
checkPeers(peers);
userContextCheck(userContext);
if (blockHash == null) {
throw new InvalidArgumentException("blockHash parameter is null.");
}
try {
logger.trace("queryBlockByHash with hash : " + Hex.encodeHexString(blockHash) + " on channel " + name);
QuerySCCRequest querySCCRequest = new QuerySCCRequest(userContext);
querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYHASH);
querySCCRequest.setArgs(name);
querySCCRequest.setArgBytes(new byte[][] {blockHash});
ProposalResponse proposalResponse = sendProposalSerially(querySCCRequest, peers);
return new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload()));
} catch (InvalidProtocolBufferException e) {
ProposalException proposalException = new ProposalException(e);
logger.error(proposalException);
throw proposalException;
}
}
private Peer getRandomLedgerQueryPeer() throws InvalidArgumentException {
final ArrayList ledgerQueryPeers = new ArrayList<>(new HashSet<>(getLedgerQueryPeers())); //copy to avoid unlikely changes
if (ledgerQueryPeers.isEmpty()) {
throw new InvalidArgumentException("Channel " + name + " does not have any ledger querying peers associated with it.");
}
return ledgerQueryPeers.get(RANDOM.nextInt(ledgerQueryPeers.size()));
}
private Peer getRandomPeer() throws InvalidArgumentException {
final ArrayList randPicks = new ArrayList<>(getPeers()); //copy to avoid unlikely changes
if (randPicks.isEmpty()) {
throw new InvalidArgumentException("Channel " + name + " does not have any peers associated with it.");
}
return randPicks.get(RANDOM.nextInt(randPicks.size()));
}
private List getShuffledPeers() {
ArrayList peers = new ArrayList<>(getPeers());
Collections.shuffle(peers);
return peers;
}
private List getShuffledPeers(EnumSet roles) {
ArrayList peers = new ArrayList<>(getPeers(roles));
Collections.shuffle(peers);
return peers;
}
private Orderer getRandomOrderer() throws InvalidArgumentException {
final ArrayList randPicks = new ArrayList<>(new HashSet<>(getOrderers())); //copy to avoid unlikely changes
if (randPicks.isEmpty()) {
throw new InvalidArgumentException("Channel " + name + " does not have any orderers associated with it.");
}
return randPicks.get(RANDOM.nextInt(randPicks.size()));
}
private void checkPeer(Peer peer) throws InvalidArgumentException {
if (peer == null) {
throw new InvalidArgumentException("Peer value is null.");
}
if (isSystemChannel()) {
return; // System owns no peers
}
if (!getPeers().contains(peer)) {
throw new InvalidArgumentException("Channel " + name + " does not have peer " + peer.getName());
}
if (peer.getChannel() != this) {
throw new InvalidArgumentException("Peer " + peer.getName() + " not set for channel " + name);
}
}
private void checkOrderer(Orderer orderer) throws InvalidArgumentException {
if (orderer == null) {
throw new InvalidArgumentException("Orderer value is null.");
}
if (isSystemChannel()) {
return; // System owns no Orderers
}
if (!getOrderers().contains(orderer)) {
throw new InvalidArgumentException("Channel " + name + " does not have orderer " + orderer.getName());
}
if (orderer.getChannel() != this) {
throw new InvalidArgumentException("Orderer " + orderer.getName() + " not set for channel " + name);
}
}
private void checkPeers(Collection peers) throws InvalidArgumentException {
if (peers == null) {
throw new InvalidArgumentException("Collection of peers is null.");
}
if (peers.isEmpty()) {
throw new InvalidArgumentException("Collection of peers is empty.");
}
for (Peer peer : peers) {
checkPeer(peer);
}
}
/**
* query this channel for a Block by the blockNumber.
* The request is retried on all peers till successful
*
* This method may not be thread safe if client context is changed!
*
* @param blockNumber index of the Block in the chain
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(long blockNumber) throws InvalidArgumentException, ProposalException {
return queryBlockByNumber(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), blockNumber);
}
/**
* query this channel for a Block by the blockNumber.
* The request is sent to a random peer in the channel.
*
* @param blockNumber index of the Block in the chain
* @param userContext the user context to be used.
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(long blockNumber, User userContext) throws InvalidArgumentException, ProposalException {
return queryBlockByNumber(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), blockNumber, userContext);
}
/**
* Query a peer in this channel for a Block by the blockNumber
*
* This method may not be thread safe if client context is changed!
*
* @param peer the peer to send the request to
* @param blockNumber index of the Block in the chain
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(Peer peer, long blockNumber) throws InvalidArgumentException, ProposalException {
return queryBlockByNumber(Collections.singleton(peer), blockNumber);
}
/**
* query a peer in this channel for a Block by the blockNumber
*
* @param peer the peer to send the request to
* @param blockNumber index of the Block in the chain
* @param userContext the user context.
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(Peer peer, long blockNumber, User userContext) throws InvalidArgumentException, ProposalException {
return queryBlockByNumber(Collections.singleton(peer), blockNumber, userContext);
}
/**
* query a peer in this channel for a Block by the blockNumber
*
* This method may not be thread safe if client context is changed!
*
* @param peers the peers to try and send the request to
* @param blockNumber index of the Block in the chain
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(Collection peers, long blockNumber) throws InvalidArgumentException, ProposalException {
return queryBlockByNumber(peers, blockNumber, client.getUserContext());
}
/**
* query a peer in this channel for a Block by the blockNumber
*
* @param peers the peers to try and send the request to
* @param blockNumber index of the Block in the chain
* @param userContext the user context to use.
* @return the {@link BlockInfo} with the given blockNumber
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByNumber(Collection peers, long blockNumber, User userContext) throws InvalidArgumentException, ProposalException {
checkChannelState();
checkPeers(peers);
userContextCheck(userContext);
try {
logger.debug("queryBlockByNumber with blockNumber " + blockNumber + " on channel " + name);
QuerySCCRequest querySCCRequest = new QuerySCCRequest(userContext);
querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYNUMBER);
querySCCRequest.setArgs(name, Long.toUnsignedString(blockNumber));
ProposalResponse proposalResponse = sendProposalSerially(querySCCRequest, peers);
return new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload()));
} catch (InvalidProtocolBufferException e) {
logger.error(e);
throw new ProposalException(e);
}
}
/**
* query this channel for a Block by a TransactionID contained in the block
* The request is tried on on each peer till successful.
*
* This method may not be thread safe if client context is changed!
*
* @param txID the transactionID to query on
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(String txID) throws InvalidArgumentException, ProposalException {
return queryBlockByTransactionID(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), txID);
}
/**
* query this channel for a Block by a TransactionID contained in the block
* The request is sent to a random peer in the channel
*
* @param txID the transactionID to query on
* @param userContext the user context.
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(String txID, User userContext) throws InvalidArgumentException, ProposalException {
return queryBlockByTransactionID(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), txID, userContext);
}
/**
* query a peer in this channel for a Block by a TransactionID contained in the block
*
* This method may not be thread safe if client context is changed!
*
* @param peer the peer to send the request to
* @param txID the transactionID to query on
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(Peer peer, String txID) throws InvalidArgumentException, ProposalException {
return queryBlockByTransactionID(Collections.singleton(peer), txID);
}
/**
* query a peer in this channel for a Block by a TransactionID contained in the block
*
* @param peer the peer to send the request to
* @param txID the transactionID to query on
* @param userContext the user context.
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(Peer peer, String txID, User userContext) throws InvalidArgumentException, ProposalException {
return queryBlockByTransactionID(Collections.singleton(peer), txID, userContext);
}
/**
* query a peer in this channel for a Block by a TransactionID contained in the block
*
* This method may not be thread safe if client context is changed!
*
* @param peers the peers to try to send the request to.
* @param txID the transactionID to query on
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(Collection peers, String txID) throws InvalidArgumentException, ProposalException {
return queryBlockByTransactionID(peers, txID, client.getUserContext());
}
/**
* query a peer in this channel for a Block by a TransactionID contained in the block
*
* @param peers the peer to try to send the request to
* @param txID the transactionID to query on
* @param userContext the user context.
* @return the {@link BlockInfo} for the Block containing the transaction
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockInfo queryBlockByTransactionID(Collection peers, String txID, User userContext) throws InvalidArgumentException, ProposalException {
checkChannelState();
checkPeers(peers);
User.userContextCheck(userContext);
if (txID == null) {
throw new InvalidArgumentException("TxID parameter is null.");
}
try {
logger.debug("queryBlockByTransactionID with txID " + txID + " \n " + " on channel " + name);
QuerySCCRequest querySCCRequest = new QuerySCCRequest(userContext);
querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYTXID);
querySCCRequest.setArgs(name, txID);
ProposalResponse proposalResponse = sendProposalSerially(querySCCRequest, peers);
return new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload()));
} catch (InvalidProtocolBufferException e) {
throw new ProposalException(e);
}
}
/**
* query this channel for chain information.
* The request is sent to a random peer in the channel
*
*
* This method may not be thread safe if client context is changed!
*
* @return a {@link BlockchainInfo} object containing the chain info requested
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockchainInfo queryBlockchainInfo() throws ProposalException, InvalidArgumentException {
return queryBlockchainInfo(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), client.getUserContext());
}
/**
* query this channel for chain information.
* The request is sent to a random peer in the channel
*
* @param userContext the user context to use.
* @return a {@link BlockchainInfo} object containing the chain info requested
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockchainInfo queryBlockchainInfo(User userContext) throws ProposalException, InvalidArgumentException {
return queryBlockchainInfo(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), userContext);
}
/**
* query for chain information
*
*
* This method may not be thread safe if client context is changed!
*
* @param peer The peer to send the request to
* @return a {@link BlockchainInfo} object containing the chain info requested
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockchainInfo queryBlockchainInfo(Peer peer) throws ProposalException, InvalidArgumentException {
return queryBlockchainInfo(Collections.singleton(peer), client.getUserContext());
}
/**
* query for chain information
*
* @param peer The peer to send the request to
* @param userContext the user context to use.
* @return a {@link BlockchainInfo} object containing the chain info requested
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockchainInfo queryBlockchainInfo(Peer peer, User userContext) throws ProposalException, InvalidArgumentException {
return queryBlockchainInfo(Collections.singleton(peer), userContext);
}
/**
* query for chain information
*
* @param peers The peers to try send the request.
* @param userContext the user context.
* @return a {@link BlockchainInfo} object containing the chain info requested
* @throws InvalidArgumentException
* @throws ProposalException
*/
public BlockchainInfo queryBlockchainInfo(Collection peers, User userContext) throws ProposalException, InvalidArgumentException {
checkChannelState();
checkPeers(peers);
User.userContextCheck(userContext);
try {
logger.debug("queryBlockchainInfo to peer " + " on channel " + name);
QuerySCCRequest querySCCRequest = new QuerySCCRequest(userContext);
querySCCRequest.setFcn(QuerySCCRequest.GETCHAININFO);
querySCCRequest.setArgs(name);
ProposalResponse proposalResponse = sendProposalSerially(querySCCRequest, peers);
return new BlockchainInfo(Ledger.BlockchainInfo.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload()));
} catch (Exception e) {
logger.error(e);
throw new ProposalException(e);
}
}
/**
* Query this channel for a Fabric Transaction given its transactionID.
* The request is sent to a random peer in the channel.
*
*
* This method may not be thread safe if client context is changed!
*
* @param txID the ID of the transaction
* @return a {@link TransactionInfo}
* @throws ProposalException
* @throws InvalidArgumentException
*/
public TransactionInfo queryTransactionByID(String txID) throws ProposalException, InvalidArgumentException {
return queryTransactionByID(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), txID, client.getUserContext());
}
/**
* Query this channel for a Fabric Transaction given its transactionID.
* The request is sent to a random peer in the channel.
*
*
* This method may not be thread safe if client context is changed!
*
* @param txID the ID of the transaction
* @param userContext the user context used.
* @return a {@link TransactionInfo}
* @throws ProposalException
* @throws InvalidArgumentException
*/
public TransactionInfo queryTransactionByID(String txID, User userContext) throws ProposalException, InvalidArgumentException {
return queryTransactionByID(getShuffledPeers(EnumSet.of(PeerRole.LEDGER_QUERY)), txID, userContext);
}
/**
* Query for a Fabric Transaction given its transactionID
*
*
* This method may not be thread safe if client context is changed!
*
* @param txID the ID of the transaction
* @param peer the peer to send the request to
* @return a {@link TransactionInfo}
* @throws ProposalException
* @throws InvalidArgumentException
*/
public TransactionInfo queryTransactionByID(Peer peer, String txID) throws ProposalException, InvalidArgumentException {
return queryTransactionByID(Collections.singleton(peer), txID, client.getUserContext());
}
/**
* Query for a Fabric Transaction given its transactionID
*
* @param peer the peer to send the request to
* @param txID the ID of the transaction
* @param userContext the user context
* @return a {@link TransactionInfo}
* @throws ProposalException
* @throws InvalidArgumentException
*/
public TransactionInfo queryTransactionByID(Peer peer, String txID, User userContext) throws ProposalException, InvalidArgumentException {
return queryTransactionByID(Collections.singleton(peer), txID, userContext);
}
/**
* Query for a Fabric Transaction given its transactionID
*
* @param txID the ID of the transaction
* @param peers the peers to try to send the request.
* @param userContext the user context
* @return a {@link TransactionInfo}
* @throws ProposalException
* @throws InvalidArgumentException
*/
public TransactionInfo queryTransactionByID(Collection peers, String txID, User userContext) throws ProposalException, InvalidArgumentException {
checkChannelState();
checkPeers(peers);
User.userContextCheck(userContext);
if (txID == null) {
throw new InvalidArgumentException("TxID parameter is null.");
}
TransactionInfo transactionInfo;
try {
logger.debug("queryTransactionByID with txID " + txID + "\n from peer " + " on channel " + name);
QuerySCCRequest querySCCRequest = new QuerySCCRequest(userContext);
querySCCRequest.setFcn(QuerySCCRequest.GETTRANSACTIONBYID);
querySCCRequest.setArgs(name, txID);
ProposalResponse proposalResponse = sendProposalSerially(querySCCRequest, peers);
return new TransactionInfo(txID, ProcessedTransaction.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload()));
} catch (Exception e) {
logger.error(e);
throw new ProposalException(e);
}
}
/////////////////////////////////////////////////////////
// transactions order
Set queryChannels(Peer peer) throws InvalidArgumentException, ProposalException {
checkPeer(peer);
if (!isSystemChannel()) {
throw new InvalidArgumentException("queryChannels should only be invoked on system channel.");
}
try {
TransactionContext context = getTransactionContext();
FabricProposal.Proposal q = QueryPeerChannelsBuilder.newBuilder().context(context).build();
SignedProposal qProposal = getSignedProposal(context, q);
Collection proposalResponses = sendProposalToPeers(Collections.singletonList(peer), qProposal, context);
if (null == proposalResponses) {
throw new ProposalException(format("Peer %s channel query return with null for responses", peer.getName()));
}
if (proposalResponses.size() != 1) {
throw new ProposalException(format("Peer %s channel query expected one response but got back %d responses ", peer.getName(), proposalResponses.size()));
}
ProposalResponse proposalResponse = proposalResponses.iterator().next();
if (proposalResponse.getStatus() != ChaincodeResponse.Status.SUCCESS) {
throw new ProposalException(format("Failed exception message is %s, status is %d", proposalResponse.getMessage(), proposalResponse.getStatus().getStatus()));
}
FabricProposalResponse.ProposalResponse fabricResponse = proposalResponse.getProposalResponse();
if (null == fabricResponse) {
throw new ProposalException(format("Peer %s channel query return with empty fabric response", peer.getName()));
}
final Response fabricResponseResponse = fabricResponse.getResponse();
if (null == fabricResponseResponse) { //not likely but check it.
throw new ProposalException(format("Peer %s channel query return with empty fabricResponseResponse", peer.getName()));
}
if (200 != fabricResponseResponse.getStatus()) {
throw new ProposalException(format("Peer %s channel query expected 200, actual returned was: %d. "
+ fabricResponseResponse.getMessage(), peer.getName(), fabricResponseResponse.getStatus()));
}
ChannelQueryResponse qr = ChannelQueryResponse.parseFrom(fabricResponseResponse.getPayload());
Set ret = new HashSet<>(qr.getChannelsCount());
for (Query.ChannelInfo x : qr.getChannelsList()) {
ret.add(x.getChannelId());
}
return ret;
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
throw new ProposalException(format("Query for peer %s channels failed. " + e.getMessage(), name), e);
}
}
List queryInstalledChaincodes(Peer peer) throws InvalidArgumentException, ProposalException {
checkPeer(peer);
if (!isSystemChannel()) {
throw new InvalidArgumentException("queryInstalledChaincodes should only be invoked on system channel.");
}
try {
TransactionContext context = getTransactionContext();
FabricProposal.Proposal q = QueryInstalledChaincodesBuilder.newBuilder().context(context).build();
SignedProposal qProposal = getSignedProposal(context, q);
Collection proposalResponses = sendProposalToPeers(Collections.singletonList(peer), qProposal, context);
if (null == proposalResponses) {
throw new ProposalException(format("Peer %s channel query return with null for responses", peer.getName()));
}
if (proposalResponses.size() != 1) {
throw new ProposalException(format("Peer %s channel query expected one response but got back %d responses ", peer.getName(), proposalResponses.size()));
}
ProposalResponse proposalResponse = proposalResponses.iterator().next();
FabricProposalResponse.ProposalResponse fabricResponse = proposalResponse.getProposalResponse();
if (null == fabricResponse) {
throw new ProposalException(format("Peer %s channel query return with empty fabric response", peer.getName()));
}
final Response fabricResponseResponse = fabricResponse.getResponse();
if (null == fabricResponseResponse) { //not likely but check it.
throw new ProposalException(format("Peer %s channel query return with empty fabricResponseResponse", peer.getName()));
}
if (200 != fabricResponseResponse.getStatus()) {
throw new ProposalException(format("Peer %s channel query expected 200, actual returned was: %d. "
+ fabricResponseResponse.getMessage(), peer.getName(), fabricResponseResponse.getStatus()));
}
ChaincodeQueryResponse chaincodeQueryResponse = ChaincodeQueryResponse.parseFrom(fabricResponseResponse.getPayload());
return chaincodeQueryResponse.getChaincodesList();
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
throw new ProposalException(format("Query for peer %s channels failed. " + e.getMessage(), name), e);
}
}
/**
* Query peer for chaincode that has been instantiated
*
* This method may not be thread safe if client context is changed!
*
* @param peer The peer to query.
* @return A list of ChaincodeInfo @see {@link ChaincodeInfo}
* @throws InvalidArgumentException
* @throws ProposalException
*/
public List queryInstantiatedChaincodes(Peer peer) throws InvalidArgumentException, ProposalException {
return queryInstantiatedChaincodes(peer, client.getUserContext());
}
/**
* Query peer for chaincode that has been instantiated
*
* @param peer The peer to query.
* @param userContext the user context.
* @return A list of ChaincodeInfo @see {@link ChaincodeInfo}
* @throws InvalidArgumentException
* @throws ProposalException
*/
public List queryInstantiatedChaincodes(Peer peer, User userContext) throws InvalidArgumentException, ProposalException {
checkChannelState();
checkPeer(peer);
User.userContextCheck(userContext);
try {
TransactionContext context = getTransactionContext(userContext);
FabricProposal.Proposal q = QueryInstantiatedChaincodesBuilder.newBuilder().context(context).build();
SignedProposal qProposal = getSignedProposal(context, q);
Collection proposalResponses = sendProposalToPeers(Collections.singletonList(peer), qProposal, context);
if (null == proposalResponses) {
throw new ProposalException(format("Peer %s channel query return with null for responses", peer.getName()));
}
if (proposalResponses.size() != 1) {
throw new ProposalException(format("Peer %s channel query expected one response but got back %d responses ", peer.getName(), proposalResponses.size()));
}
ProposalResponse proposalResponse = proposalResponses.iterator().next();
FabricProposalResponse.ProposalResponse fabricResponse = proposalResponse.getProposalResponse();
if (null == fabricResponse) {
throw new ProposalException(format("Peer %s channel query return with empty fabric response", peer.getName()));
}
final Response fabricResponseResponse = fabricResponse.getResponse();
if (null == fabricResponseResponse) { //not likely but check it.
throw new ProposalException(format("Peer %s channel query return with empty fabricResponseResponse", peer.getName()));
}
if (200 != fabricResponseResponse.getStatus()) {
throw new ProposalException(format("Peer %s channel query expected 200, actual returned was: %d. "
+ fabricResponseResponse.getMessage(), peer.getName(), fabricResponseResponse.getStatus()));
}
ChaincodeQueryResponse chaincodeQueryResponse = ChaincodeQueryResponse.parseFrom(fabricResponseResponse.getPayload());
return chaincodeQueryResponse.getChaincodesList();
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
throw new ProposalException(format("Query for peer %s channels failed. " + e.getMessage(), name), e);
}
}
/**
* Get information on the collections used by the chaincode.
*
* @param chaincodeName The name of the chaincode to query.
* @param peer Peer to query.
* @param userContext The context of the user to sign the request.
* @return CollectionConfigPackage with information on the collection used by the chaincode.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public CollectionConfigPackage queryCollectionsConfig(String chaincodeName, Peer peer, User userContext) throws InvalidArgumentException, ProposalException {
if (Utils.isNullOrEmpty(chaincodeName)) {
throw new InvalidArgumentException("Parameter chaincodeName expected to be non null or empty string.");
}
checkChannelState();
checkPeer(peer);
User.userContextCheck(userContext);
try {
TransactionContext context = getTransactionContext(userContext);
QueryCollectionsConfigBuilder queryCollectionsConfigBuilder = QueryCollectionsConfigBuilder.newBuilder()
.context(context).chaincodeName(chaincodeName);
FabricProposal.Proposal q = queryCollectionsConfigBuilder.build();
SignedProposal qProposal = getSignedProposal(context, q);
Collection proposalResponses = sendProposalToPeers(Collections.singletonList(peer), qProposal, context);
if (null == proposalResponses) {
throw new ProposalException(format("Peer %s channel query return with null for responses", peer.getName()));
}
if (proposalResponses.size() != 1) {
throw new ProposalException(format("Peer %s channel query expected one response but got back %d responses ", peer.getName(), proposalResponses.size()));
}
ProposalResponse proposalResponse = proposalResponses.iterator().next();
FabricProposalResponse.ProposalResponse fabricResponse = proposalResponse.getProposalResponse();
if (null == fabricResponse) {
throw new ProposalException(format("Peer %s channel query return with empty fabric response", peer.getName()));
}
final Response fabricResponseResponse = fabricResponse.getResponse();
if (null == fabricResponseResponse) { //not likely but check it.
throw new ProposalException(format("Peer %s channel query return with empty fabricResponseResponse", peer.getName()));
}
if (200 != fabricResponseResponse.getStatus()) {
throw new ProposalException(format("Peer %s channel query expected 200, actual returned was: %d. "
+ fabricResponseResponse.getMessage(), peer.getName(), fabricResponseResponse.getStatus()));
}
return new CollectionConfigPackage(fabricResponseResponse.getPayload());
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
throw new ProposalException(format("Query for peer %s channels failed. " + e.getMessage(), name), e);
}
}
/**
* Send a transaction proposal.
*
* @param transactionProposalRequest The transaction proposal to be sent to all the required peers needed for endorsing.
* @return responses from peers.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection sendTransactionProposal(TransactionProposalRequest transactionProposalRequest) throws ProposalException, InvalidArgumentException {
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.
*
* @param transactionProposalRequest The transaction proposal to be sent to all the required peers needed for endorsing.
* @param discoveryOptions
* @return responses from peers.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection sendTransactionProposalToEndorsers(TransactionProposalRequest transactionProposalRequest, DiscoveryOptions discoveryOptions) throws ProposalException, InvalidArgumentException, ServiceDiscoveryException {
final String chaincodeName = transactionProposalRequest.getChaincodeID().getName();
checkChannelState();
if (null == transactionProposalRequest) {
throw new InvalidArgumentException("The proposalRequest is null");
}
if (isNullOrEmpty(transactionProposalRequest.getFcn())) {
throw new InvalidArgumentException("The proposalRequest's fcn is null or empty.");
}
if (transactionProposalRequest.getChaincodeID() == null) {
throw new InvalidArgumentException("The proposalRequest's chaincode ID is null");
}
logger.debug(format("Channel %s sendTransactionProposalToEndorsers chaincode name: %s", name, chaincodeName));
TransactionContext transactionContext = getTransactionContext(transactionProposalRequest.getUserContext());
transactionContext.verify(transactionProposalRequest.doVerify());
transactionContext.setProposalWaitTime(transactionProposalRequest.getProposalWaitTime());
// Protobuf message builder
ProposalBuilder proposalBuilder = ProposalBuilder.newBuilder();
proposalBuilder.context(transactionContext);
proposalBuilder.request(transactionProposalRequest);
SignedProposal invokeProposal = null;
try {
invokeProposal = getSignedProposal(transactionContext, proposalBuilder.build());
} catch (CryptoException e) {
throw new InvalidArgumentException(e);
}
SDChaindcode sdChaindcode;
final List serviceDiscoveryChaincodeInterests = discoveryOptions.getServiceDiscoveryChaincodeInterests();
if (null != serviceDiscoveryChaincodeInterests && !serviceDiscoveryChaincodeInterests.isEmpty()) {
final String firstname = serviceDiscoveryChaincodeInterests.get(0).getName();
if (!firstname.equals(chaincodeName)) {
serviceDiscoveryChaincodeInterests.add(0, new ServiceDiscoveryChaincodeCalls(chaincodeName));
}
List> ccl = new LinkedList<>();
ccl.add(serviceDiscoveryChaincodeInterests);
final Map sdChaindcodeMap = serviceDiscovery.discoverEndorserEndpoints(transactionContext, ccl);
if (sdChaindcodeMap == null) {
throw new ServiceDiscoveryException(format("Channel %s failed doing service discovery for chaincode %s ", name, chaincodeName));
}
sdChaindcode = sdChaindcodeMap.get(chaincodeName);
} else {
if (discoveryOptions.forceDiscovery) {
logger.trace("Forcing discovery.");
serviceDiscovery.networkDiscovery(transactionContext, true);
}
sdChaindcode = serviceDiscovery.discoverEndorserEndpoint(transactionContext, chaincodeName);
}
logger.trace(format("Channel %s chaincode %s discovered: %s", name, chaincodeName, "" + sdChaindcode));
if (null == sdChaindcode) {
throw new ServiceDiscoveryException(format("Channel %s failed to find and endorsers for chaincode %s", name, chaincodeName));
}
if (sdChaindcode.getLayouts() == null || sdChaindcode.getLayouts().isEmpty()) {
throw new ServiceDiscoveryException(format("Channel %s failed to find and endorsers for chaincode %s no layouts found.", name, chaincodeName));
}
SDChaindcode sdChaindcodeEndorsementCopy = new SDChaindcode(sdChaindcode); //copy. no ignored.
final boolean inspectResults = discoveryOptions.inspectResults;
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", sdChaindcodeEndorsementCopy));
}
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
boolean done = false;
int attempts = 1; //safety valve
do {
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
if (IS_TRACE_LEVEL) {
StringBuilder sb = new StringBuilder(1000);
String sep = "";
for (SDEndorser sdEndorser : ep) {
sb.append(sep).append(sdEndorser);
}
logger.trace(format("Channel %s, chaincode %s attempts: %d requested endorsements: %s", name, chaincodeName, attempts, sb.toString()));
}
//Safety check make sure the selector isn't giving back endpoints to retry
ep.removeIf(sdEndorser -> goodResponses.keySet().contains(sdEndorser));
if (ep.isEmpty()) { // this would be odd but lets go with it.
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)));
if (IS_DEBUG_LEVEL) {
StringBuilder sb = new StringBuilder(1000);
String sep = "";
for (ProposalResponse proposalResponse : ret) {
sb.append(sep).append(proposalResponse.getPeer());
sep = ", ";
}
logger.debug(format("Channel %s, chaincode %s attempts: %d got all needed endorsements: %s", name, chaincodeName, attempts, sb.toString()));
}
return ret; // the happy path :)!
} else { //still don't have the needed endorsements.
logger.debug(format("Channel %s, chaincode %s attempts: %d missing needed endorsements", name, chaincodeName, attempts));
if (inspectResults) {
return allTried.values();
} else {
throw new ServiceDiscoveryException(format("Could not meet endorsement policy for chaincode %s", chaincodeName));
}
}
}
Map lpeerEndpointMap = new HashMap<>(peerEndpointMap);
Map endorsers = new HashMap<>(ep.size());
Map peer2sdEndorser = new HashMap<>(ep.size());
for (SDEndorser sdEndorser : ep) {
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(sdEndorser.getTLSCerts(), sdEndorser.getTLSIntermediateCerts());
properties.put("pemBytes", bytes);
epeer.setProperties(properties);
} else if (null == epeer) {
epeer = sdPeerAddition.addPeer(new SDPeerAdditionInfo() {
@Override
public String getMspId() {
return sdEndorser.getMspid();
}
@Override
public String getEndpoint() {
return sdEndorser.getEndpoint();
}
@Override
public Channel getChannel() {
return Channel.this;
}
@Override
public HFClient getClient() {
return Channel.this.client;
}
@Override
public byte[][] getTLSCerts() {
return sdEndorser.getTLSCerts().toArray(new byte[sdEndorser.getTLSCerts().size()][]);
}
@Override
public byte[][] getTLSIntermediateCerts() {
return sdEndorser.getTLSIntermediateCerts().toArray(new byte[sdEndorser.getTLSIntermediateCerts().size()][]);
}
@Override
public Map getEndpointMap() {
return Collections.unmodifiableMap(Channel.this.peerEndpointMap);
}
});
}
endorsers.put(sdEndorser, epeer);
peer2sdEndorser.put(new PeerExactMatch(epeer), sdEndorser); // reverse
}
final Collection proposalResponses = sendProposalToPeers(endorsers.values(), invokeProposal, transactionContext);
HashSet loopGood = new HashSet<>();
HashSet loopBad = new HashSet<>();
for (ProposalResponse proposalResponse : proposalResponses) {
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(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, sdEndorser));
loopBad.add(sdEndorser);
}
}
//Always check on original
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) {
StringBuilder sb = new StringBuilder(1000);
String sep = "";
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.
sdChaindcodeEndorsementCopy.endorsedList(loopGood); // mark the good ones in the working copy.
if (sdChaindcodeEndorsementCopy.ignoreListSDEndorser(loopBad) < 1) { // apply ignore list
done = true; // no more layouts
}
}
} while (!done && ++attempts <= 5);
logger.debug(format("Endorsements not achieved chaincode: %s, done: %b, attempts: %d", chaincodeName, done, attempts));
if (inspectResults) {
return allTried.values();
} else {
throw new ServiceDiscoveryException(format("Could not meet endorsement policy for chaincode %s", chaincodeName));
}
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
ProposalException exp = new ProposalException(e);
logger.error(exp.getMessage(), exp);
throw exp;
}
}
/**
* Collection of discovered chaincode names.
*
* @return
*/
public Collection getDiscoveredChaincodeNames() {
if (serviceDiscovery == null) {
return Collections.EMPTY_LIST;
}
return serviceDiscovery.getDiscoveredChaincodeNames();
}
/**
* Send a transaction proposal to specific peers.
*
* @param transactionProposalRequest The transaction proposal to be sent to the peers.
* @param peers
* @return responses from peers.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection sendTransactionProposal(TransactionProposalRequest transactionProposalRequest, Collection peers) throws ProposalException, InvalidArgumentException {
return sendProposal(transactionProposalRequest, peers);
}
/**
* Send Query proposal
*
* @param queryByChaincodeRequest
* @return Collection proposal responses.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection queryByChaincode(QueryByChaincodeRequest queryByChaincodeRequest) throws InvalidArgumentException, ProposalException {
return queryByChaincode(queryByChaincodeRequest, getChaincodeQueryPeers());
}
/**
* Send Query proposal
*
* @param queryByChaincodeRequest
* @param peers
* @return responses from peers.
* @throws InvalidArgumentException
* @throws ProposalException
*/
public Collection queryByChaincode(QueryByChaincodeRequest queryByChaincodeRequest, Collection peers) throws InvalidArgumentException, ProposalException {
return sendProposal(queryByChaincodeRequest, peers);
}
//////////////// Channel Block monitoring //////////////////////////////////
private ProposalResponse sendProposalSerially(TransactionRequest proposalRequest, Collection peers) throws
ProposalException {
ProposalException lastException = new ProposalException("ProposalRequest failed.");
for (Peer peer : peers) {
proposalRequest.submitted = false;
try {
Collection proposalResponses = sendProposal(proposalRequest, Collections.singletonList(peer));
if (proposalResponses.isEmpty()) {
logger.warn(format("Proposal request to peer %s failed", peer));
}
ProposalResponse proposalResponse = proposalResponses.iterator().next();
ChaincodeResponse.Status status = proposalResponse.getStatus();
if (status.getStatus() < 400) {
return proposalResponse;
} else if (status.getStatus() > 499) { // server error may work on other peer.
lastException = new ProposalException(format("Channel %s got exception on peer %s %d. %s ",
name,
peer,
status.getStatus(),
proposalResponse.getMessage()));
} else { // 400 to 499
throw new ProposalException(format("Channel %s got exception on peer %s %d. %s ",
name,
peer,
status.getStatus(),
proposalResponse.getMessage()));
}
} catch (Exception e) {
lastException = new ProposalException(format("Channel %s failed proposal on peer %s %s",
name,
peer.getName(),
e.getMessage()), e);
logger.warn(lastException.getMessage());
}
}
throw lastException;
}
private Collection sendProposal(TransactionRequest proposalRequest, Collection peers) throws
InvalidArgumentException, ProposalException {
checkChannelState();
checkPeers(peers);
if (null == proposalRequest) {
throw new InvalidArgumentException("The proposalRequest is null");
}
if (isNullOrEmpty(proposalRequest.getFcn())) {
throw new InvalidArgumentException("The proposalRequest's fcn is null or empty.");
}
if (proposalRequest.getChaincodeID() == null) {
throw new InvalidArgumentException("The proposalRequest's chaincode ID is null");
}
proposalRequest.setSubmitted();
try {
TransactionContext transactionContext = getTransactionContext(proposalRequest.getUserContext());
transactionContext.verify(proposalRequest.doVerify());
transactionContext.setProposalWaitTime(proposalRequest.getProposalWaitTime());
// Protobuf message builder
ProposalBuilder proposalBuilder = ProposalBuilder.newBuilder();
proposalBuilder.context(transactionContext);
proposalBuilder.request(proposalRequest);
SignedProposal invokeProposal = getSignedProposal(transactionContext, proposalBuilder.build());
return sendProposalToPeers(peers, invokeProposal, transactionContext);
} catch (ProposalException e) {
throw e;
} catch (Exception e) {
ProposalException exp = new ProposalException(e);
logger.error(exp.getMessage(), exp);
throw exp;
}
}
private transient ServiceDiscovery.EndorsementSelector endorsementSelector = ServiceDiscovery.DEFAULT_ENDORSEMENT_SELECTION;
public ServiceDiscovery.EndorsementSelector setSDEndorserSelector(ServiceDiscovery.EndorsementSelector endorsementSelector) {
ServiceDiscovery.EndorsementSelector ret = this.endorsementSelector;
this.endorsementSelector = endorsementSelector;
return ret;
}
private Collection sendProposalToPeers(Collection peers,
SignedProposal signedProposal,
TransactionContext transactionContext) throws InvalidArgumentException, ProposalException {
checkPeers(peers);
if (transactionContext.getVerify()) {
try {
loadCACertificates(false);
} catch (Exception e) {
throw new ProposalException(e);
}
}
final String txID = transactionContext.getTxID();
class Pair {
private final Peer peer;
private final Future future;
private Pair(Peer peer, Future future) {
this.peer = peer;
this.future = future;
}
}
List peerFuturePairs = new ArrayList<>();
for (Peer peer : peers) {
logger.debug(format("Channel %s send proposal to %s, txID: %s",
name, peer.toString(), txID));
if (null != diagnosticFileDumper) {
logger.trace(format("Sending to channel %s, peer: %s, proposal: %s, txID: %s", name, peer, txID,
diagnosticFileDumper.createDiagnosticProtobufFile(signedProposal.toByteArray())));
}
Future proposalResponseListenableFuture;
try {
proposalResponseListenableFuture = peer.sendProposalAsync(signedProposal);
} catch (Exception e) {
proposalResponseListenableFuture = new CompletableFuture<>();
((CompletableFuture) proposalResponseListenableFuture).completeExceptionally(e);
}
peerFuturePairs.add(new Pair(peer, proposalResponseListenableFuture));
}
Collection proposalResponses = new ArrayList<>();
for (Pair peerFuturePair : peerFuturePairs) {
FabricProposalResponse.ProposalResponse fabricResponse = null;
String message;
int status = 500;
final String peerName = peerFuturePair.peer.toString();
try {
fabricResponse = peerFuturePair.future.get(transactionContext.getProposalWaitTime(), TimeUnit.MILLISECONDS);
message = fabricResponse.getResponse().getMessage();
status = fabricResponse.getResponse().getStatus();
peerFuturePair.peer.setHasConnected();
logger.debug(format("Channel %s, transaction: %s got back from peer %s status: %d, message: %s",
name, txID, peerName, status, message));
if (null != diagnosticFileDumper) {
logger.trace(format("Got back from channel %s, peer: %s, proposal response: %s", name, peerName,
diagnosticFileDumper.createDiagnosticProtobufFile(fabricResponse.toByteArray())));
}
} catch (InterruptedException e) {
message = "Sending proposal with transaction: " + txID + " to " + peerName + " failed because of interruption";
logger.error(message, e);
} catch (TimeoutException e) {
message = format("Channel %s sending proposal with transaction %s to %s failed because of timeout(%d milliseconds) expiration",
toString(), txID, peerName, transactionContext.getProposalWaitTime());
logger.error(message, e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof Error) {
String emsg = "Sending proposal with txID: " + txID + " to " + peerName + " failed because of " + cause.getMessage();
logger.error(emsg, new Exception(cause)); //wrapped in exception to get full stack trace.
throw (Error) cause;
} else {
if (cause instanceof StatusRuntimeException) {
message = format("Channel %s Sending proposal with transaction: %s to %s failed because of: gRPC failure=%s",
toString(), txID, peerName, ((StatusRuntimeException) cause).getStatus());
} else {
message = format("Channel %s sending proposal with transaction: %s to %s failed because of: %s",
toString(), txID, peerName, cause.getMessage());
}
logger.error(message, new Exception(cause)); //wrapped in exception to get full stack trace.
}
}
ProposalResponse proposalResponse = new ProposalResponse(transactionContext, status, message);
proposalResponse.setProposalResponse(fabricResponse);
proposalResponse.setProposal(signedProposal);
proposalResponse.setPeer(peerFuturePair.peer);
if (fabricResponse != null && transactionContext.getVerify()) {
proposalResponse.verify(client.getCryptoSuite());
}
proposalResponses.add(proposalResponse);
}
return proposalResponses;
}
/**
* Send transaction to one of the orderers on the channel using a specific user context.
*
* @param proposalResponses The proposal responses to be sent to the orderer.
* @param userContext The usercontext used for signing transaction.
* @return a future allowing access to the result of the transaction invocation once complete.
*/
public CompletableFuture sendTransaction(Collection proposalResponses, User userContext) {
return sendTransaction(proposalResponses, getOrderers(), userContext);
}
/**
* Send transaction to one of the orderers on the channel using the usercontext set on the client.
*
* @param proposalResponses .
* @return a future allowing access to the result of the transaction invocation once complete.
*/
public CompletableFuture sendTransaction(Collection proposalResponses) {
return sendTransaction(proposalResponses, getOrderers());
}
/**
* Send transaction to one of the specified orderers using the usercontext set on the client..
*
* @param proposalResponses The proposal responses to be sent to the orderer
* @param orderers The orderers to send the transaction to.
* @return a future allowing access to the result of the transaction invocation once complete.
*/
public CompletableFuture sendTransaction(Collection proposalResponses, Collection orderers) {
return sendTransaction(proposalResponses, orderers, client.getUserContext());
}
/**
* NofEvents may be used with @see {@link TransactionOptions#nOfEvents(NOfEvents)} to control how reporting Peer service events and Eventhubs will
* complete the future acknowledging the transaction has been seen by those Peers.
*
* You can use the method @see {@link #nofNoEvents} to create an NOEvents that will result in the future being completed immediately
* when the Orderer has accepted the transaction. Note in this case the transaction event will be set to null.
*
* NofEvents can add Peer Eventing services and Eventhubs that should complete the future. By default all will need to
* see the transactions to complete the future. The method @see {@link #setN(int)} can set how many in the group need to see the transaction
* completion. Essentially setting it to 1 is any.
*
* NofEvents may also contain other NofEvent grouping. They can be nested.
*/
public static class NOfEvents {
public NOfEvents setN(int n) {
if (n < 1) {
throw new IllegalArgumentException(format("N was %d but needs to be greater than 0. ", n));
}
this.n = n;
return this;
}
boolean ready = false;
boolean started = false;
private long n = Long.MAX_VALUE; //all
private HashSet eventHubs = new HashSet<>();
private HashSet peers = new HashSet<>();
private HashSet nOfEvents = new HashSet<>();
/**
* Peers that need to see the transaction event to complete.
*
* @param peers The peers that need to see the transaction event to complete.
* @return This NofEvents.
*/
public NOfEvents addPeers(Peer... peers) {
if (peers == null || peers.length == 0) {
throw new IllegalArgumentException("Peers added must be not null or empty.");
}
this.peers.addAll(Arrays.asList(peers));
return this;
}
/**
* Peers that need to see the transaction event to complete.
*
* @param peers The peers that need to see the transaction event to complete.
* @return This NofEvents.
*/
public NOfEvents addPeers(Collection peers) {
addPeers(peers.toArray(new Peer[peers.size()]));
return this;
}
/**
* EventHubs that need to see the transaction event to complete.
* @param eventHubs The peers that need to see the transaction event to complete.
* @return This NofEvents.
*/
public NOfEvents addEventHubs(EventHub... eventHubs) {
if (eventHubs == null || eventHubs.length == 0) {
throw new IllegalArgumentException("EventHubs added must be not null or empty.");
}
this.eventHubs.addAll(Arrays.asList(eventHubs));
return this;
}
/**
* EventHubs that need to see the transaction event to complete.
* @param eventHubs The peers that need to see the transaction event to complete.
* @return This NofEvents.
*/
public NOfEvents addEventHubs(Collection eventHubs) {
addEventHubs(eventHubs.toArray(new EventHub[eventHubs.size()]));
return this;
}
/**
* NOfEvents that need to see the transaction event to complete.
* @param nOfEvents The nested event group that need to set the transacton event to complete.
* @return This NofEvents.
*/
public NOfEvents addNOfs(NOfEvents... nOfEvents) {
if (nOfEvents == null || nOfEvents.length == 0) {
throw new IllegalArgumentException("nofEvents added must be not null or empty.");
}
for (NOfEvents n : nOfEvents) {
if (nofNoEvents == n) {
throw new IllegalArgumentException("nofNoEvents may not be added as an event.");
}
if (inHayStack(n)) {
throw new IllegalArgumentException("nofEvents already was added..");
}
this.nOfEvents.add(new NOfEvents(n));
}
return this;
}
private boolean inHayStack(NOfEvents needle) {
if (this == needle) {
return true;
}
for (NOfEvents straw : nOfEvents) {
if (straw.inHayStack(needle)) {
return true;
}
}
return false;
}
/**
* NOfEvents that need to see the transaction event to complete.
* @param nofs The nested event group that need to set the transacton event to complete.
* @return This NofEvents.
*/
public NOfEvents addNOfs(Collection nofs) {
addNOfs(nofs.toArray(new NOfEvents[nofs.size()]));
return this;
}
synchronized Collection unSeenPeers() {
Set unseen = new HashSet(16);
unseen.addAll(peers);
for (NOfEvents nOfEvents : nOfEvents) {
unseen.addAll(nofNoEvents.unSeenPeers());
}
return unseen;
}
synchronized Collection unSeenEventHubs() {
Set unseen = new HashSet(16);
unseen.addAll(eventHubs);
for (NOfEvents nOfEvents : nOfEvents) {
unseen.addAll(nofNoEvents.unSeenEventHubs());
}
return unseen;
}
synchronized boolean seen(EventHub eventHub) {
if (!started) {
started = true;
n = Long.min(eventHubs.size() + peers.size() + nOfEvents.size(), n);
}
if (!ready) {
if (eventHubs.remove(eventHub)) {
if (--n == 0) {
ready = true;
}
}
if (!ready) {
for (Iterator ni = nOfEvents.iterator(); ni.hasNext();
) { // for check style
NOfEvents e = ni.next();
if (e.seen(eventHub)) {
ni.remove();
if (--n == 0) {
ready = true;
break;
}
}
}
}
}
if (ready) {
eventHubs.clear();
peers.clear();
nOfEvents.clear();
}
return ready;
}
synchronized boolean seen(Peer peer) {
if (!started) {
started = true;
n = Long.min(eventHubs.size() + peers.size() + nOfEvents.size(), n);
}
if (!ready) {
if (peers.remove(peer)) {
if (--n == 0) {
ready = true;
}
}
if (!ready) {
for (Iterator ni = nOfEvents.iterator(); ni.hasNext();
) { // for check style
NOfEvents e = ni.next();
if (e.seen(peer)) {
ni.remove();
if (--n == 0) {
ready = true;
break;
}
}
}
}
}
if (ready) {
eventHubs.clear();
peers.clear();
nOfEvents.clear();
}
return ready;
}
NOfEvents(NOfEvents nof) { // Deep Copy.
if (nofNoEvents == nof) {
throw new IllegalArgumentException("nofNoEvents may not be copied.");
}
ready = false; // no use in one set to ready.
started = false;
this.n = nof.n;
this.peers = new HashSet<>(nof.peers);
this.eventHubs = new HashSet<>(nof.eventHubs);
for (NOfEvents nofc : nof.nOfEvents) {
this.nOfEvents.add(new NOfEvents(nofc));
}
}
private NOfEvents() {
}
public static NOfEvents createNofEvents() {
return new NOfEvents();
}
/**
* Special NofEvents indicating that no transaction events are needed to complete the Future.
* This will result in the Future being completed as soon has the Orderer has seen the transaction.
*/
public static NOfEvents nofNoEvents = new NOfEvents() {
@Override
public NOfEvents addNOfs(NOfEvents... nOfEvents) {
throw new IllegalArgumentException("Can not add any events.");
}
@Override
public NOfEvents addEventHubs(EventHub... eventHub) {
throw new IllegalArgumentException("Can not add any events.");
}
@Override
public NOfEvents addPeers(Peer... peers) {
throw new IllegalArgumentException("Can not add any events.");
}
@Override
public NOfEvents setN(int n) {
throw new IllegalArgumentException("Can not set N");
}
@Override
public NOfEvents addEventHubs(Collection eventHubs) {
throw new IllegalArgumentException("Can not add any events.");
}
@Override
public NOfEvents addPeers(Collection peers) {
throw new IllegalArgumentException("Can not add any events.");
}
};
static {
nofNoEvents.ready = true;
}
public static NOfEvents createNoEvents() {
return nofNoEvents;
}
}
/**
* Send transaction to one of a specified set of orderers with the specified user context.
* IF there are no event hubs or eventing peers this future returns immediately completed
* indicating that orderer has accepted the transaction only.
*
* @param proposalResponses
* @param orderers
* @return Future allowing access to the result of the transaction invocation.
*/
public CompletableFuture sendTransaction(Collection proposalResponses, Collection orderers, User userContext) {
return sendTransaction(proposalResponses, createTransactionOptions().orderers(orderers).userContext(userContext));
}
/**
* TransactionOptions class can be used to change how the SDK processes the Transaction.
*/
public static class TransactionOptions {
List orderers;
boolean shuffleOrders = true;
NOfEvents nOfEvents;
User userContext;
boolean failFast = true;
/**
* Fail fast when there is an invalid transaction received on the eventhub or eventing peer being observed.
* The default value is true.
*
* @param failFast fail fast.
* @return This TransactionOptions
*/
public TransactionOptions failFast(boolean failFast) {
this.failFast = failFast;
return this;
}
/**
* The user context that is to be used. The default is the user context on the client.
*
* @param userContext
* @return This TransactionOptions
*/
public TransactionOptions userContext(User userContext) {
this.userContext = userContext;
return this;
}
/**
* The orders to try on this transaction. Each order is tried in turn for a successful submission.
* The default is try all orderers on the chain.
*
* @param orderers the orderers to try.
* @return This TransactionOptions
*/
public TransactionOptions orderers(Orderer... orderers) {
this.orderers = new ArrayList(Arrays.asList(orderers)); //convert make sure we have a copy.
return this;
}
/**
* Shuffle the order the Orderers are tried. The default is true.
*
* @param shuffleOrders
* @return This TransactionOptions
*/
public TransactionOptions shuffleOrders(boolean shuffleOrders) {
this.shuffleOrders = shuffleOrders;
return this;
}
/**
* Events reporting Eventing Peers and EventHubs to complete the transaction.
* This maybe set to NOfEvents.nofNoEvents that will complete the future as soon as a successful submission
* to an Orderer, but the completed Transaction event in that case will be null.
*
* @param nOfEvents @see {@link NOfEvents}
* @return This TransactionOptions
*/
public TransactionOptions nOfEvents(NOfEvents nOfEvents) {
this.nOfEvents = nOfEvents == NOfEvents.nofNoEvents ? nOfEvents : new NOfEvents(nOfEvents);
return this;
}
/**
* Create transaction options.
*
* @return return transaction options.
*/
public static TransactionOptions createTransactionOptions() {
return new TransactionOptions();
}
/**
* The orders to try on this transaction. Each order is tried in turn for a successful submission.
* The default is try all orderers on the chain.
*
* @param orderers the orderers to try.
* @return This TransactionOptions
*/
public TransactionOptions orderers(Collection orderers) {
return orderers(orderers.toArray(new Orderer[orderers.size()]));
}
}
/**
* Additional metadata used by service discovery to find the endorsements needed.
* Specify which chaincode is invoked and what collections are used.
*/
public static class ServiceDiscoveryChaincodeCalls {
String name;
List collections;
ServiceDiscoveryChaincodeCalls(String chaincodeName) {
this.name = chaincodeName;
}
/**
* The collections used by this chaincode.
*
* @param collectionName name of collection.
* @return
*/
public ServiceDiscoveryChaincodeCalls addCollections(String... collectionName) {
if (collections == null) {
collections = new LinkedList<>();
}
collections.addAll(new ArrayList<>(Arrays.asList(collectionName)));
return this;
}
String write(List dep) {
StringBuilder cns = new StringBuilder(1000);
cns.append("ServiceDiscoveryChaincodeCalls(name: ").append(name);
String sep = "";
final List collections = getCollections();
if (!collections.isEmpty()) {
cns.append(", collections:[");
String sep2 = "";
for (String collection : collections) {
cns.append(sep2).append(collection);
sep2 = ", ";
}
cns.append("]");
}
if (dep != null && !dep.isEmpty()) {
cns.append(" ,dependents:[");
String sep2 = "";
for (ServiceDiscoveryChaincodeCalls chaincodeCalls : dep) {
cns.append(sep2).append(chaincodeCalls.write(null));
sep2 = ", ";
}
cns.append("]");
}
cns.append(")");
return cns.toString();
}
/**
* Create ch
*
* @param name
* @return
* @throws InvalidArgumentException
*/
public static ServiceDiscoveryChaincodeCalls createServiceDiscoveryChaincodeCalls(String name) throws InvalidArgumentException {
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("The name paramter must be non null nor an empty string.");
}
return new ServiceDiscoveryChaincodeCalls(name);
}
private Protocol.ChaincodeCall ret = null;
Protocol.ChaincodeCall build() {
if (ret == null) {
final Protocol.ChaincodeCall.Builder builder = Protocol.ChaincodeCall.newBuilder().setName(name);
if (collections != null && !collections.isEmpty()) {
builder.addAllCollectionNames(collections);
}
ret = builder.build();
}
return ret;
}
String getName() {
return name;
}
List getCollections() {
return collections == null ? Collections.EMPTY_LIST : collections;
}
}
/**
* Options for doing service discovery.
*/
public static class DiscoveryOptions {
Set ignoreList = new HashSet<>();
ServiceDiscovery.EndorsementSelector endorsementSelector = null;
boolean inspectResults = false;
boolean forceDiscovery = false;
List getServiceDiscoveryChaincodeInterests() {
return serviceDiscoveryChaincodeInterests;
}
List serviceDiscoveryChaincodeInterests = null;
/**
* Create transaction options.
*
* @return return transaction options.
*/
public static DiscoveryOptions createDiscoveryOptions() {
return new DiscoveryOptions();
}
public boolean isInspectResults() {
return inspectResults;
}
/**
* Set to true to inspect proposals results on error.
*
* @param inspectResults
* @return
*/
public DiscoveryOptions setInspectResults(boolean inspectResults) {
this.inspectResults = inspectResults;
return this;
}
/**
* Set the handler which selects the endorser endpoints from the alternatives provided by service discovery.
*
* @param endorsementSelector
* @return
* @throws InvalidArgumentException
*/
public DiscoveryOptions setEndorsementSelector(ServiceDiscovery.EndorsementSelector endorsementSelector) throws InvalidArgumentException {
if (endorsementSelector == null) {
throw new InvalidArgumentException("endorsementSelector parameter is null.");
}
this.endorsementSelector = endorsementSelector;
return this;
}
/**
* Set which other chaincode calls are made by this chaincode and they're collections.
*
* @param serviceDiscoveryChaincodeInterests
* @return DiscoveryOptions
*/
public DiscoveryOptions setServiceDiscoveryChaincodeInterests(ServiceDiscoveryChaincodeCalls... serviceDiscoveryChaincodeInterests) {
if (this.serviceDiscoveryChaincodeInterests == null) {
this.serviceDiscoveryChaincodeInterests = new LinkedList<>();
}
this.serviceDiscoveryChaincodeInterests.addAll(new ArrayList<>(Arrays.asList(serviceDiscoveryChaincodeInterests)));
return this;
}
/**
* Force new service discovery
*
* @param forceDiscovery
* @return
*/
public DiscoveryOptions setForceDiscovery(boolean forceDiscovery) {
this.forceDiscovery = forceDiscovery;
return this;
}
public DiscoveryOptions ignoreEndpoints(String... endpoints) throws InvalidArgumentException {
if (endpoints == null) {
throw new InvalidArgumentException("endpoints parameter is null.");
}
for (String endpoint : endpoints) {
if (endpoint == null) {
throw new InvalidArgumentException("endpoints parameter is null.");
}
ignoreList.add(endpoint);
}
return this;
}
Collection getIgnoreList() {
return ignoreList;
}
}
/**
* Send transaction to one of a specified set of orderers with the specified user context.
* IF there are no event hubs or eventing peers this future returns immediately completed
* indicating that orderer has accepted the transaction only.
*
* @param proposalResponses
* @param transactionOptions
* @return Future allowing access to the result of the transaction invocation.
*/
public CompletableFuture sendTransaction(Collection proposalResponses,
TransactionOptions transactionOptions) {
try {
if (null == transactionOptions) {
throw new InvalidArgumentException("Parameter transactionOptions can't be null");
}
checkChannelState();
User userContext = transactionOptions.userContext != null ? transactionOptions.userContext : client.getUserContext();
userContextCheck(userContext);
if (null == proposalResponses) {
throw new InvalidArgumentException("sendTransaction proposalResponses was null");
}
List orderers = transactionOptions.orderers != null ? transactionOptions.orderers :
new ArrayList<>(getOrderers());
// make certain we have our own copy
final List shuffeledOrderers = new ArrayList<>(orderers);
if (transactionOptions.shuffleOrders) {
Collections.shuffle(shuffeledOrderers);
}
if (config.getProposalConsistencyValidation()) {
HashSet invalid = new HashSet<>();
int consistencyGroups = SDKUtils.getProposalConsistencySets(proposalResponses, invalid).size();
if (consistencyGroups != 1 || !invalid.isEmpty()) {
throw new IllegalArgumentException(format(
"The proposal responses have %d inconsistent groups with %d that are invalid."
+ " Expected all to be consistent and none to be invalid.",
consistencyGroups, invalid.size()));
}
}
List ed = new LinkedList<>();
FabricProposal.Proposal proposal = null;
ByteString proposalResponsePayload = null;
String proposalTransactionID = null;
TransactionContext transactionContext = null;
for (ProposalResponse sdkProposalResponse : proposalResponses) {
ed.add(sdkProposalResponse.getProposalResponse().getEndorsement());
if (proposal == null) {
proposal = sdkProposalResponse.getProposal();
proposalTransactionID = sdkProposalResponse.getTransactionID();
if (proposalTransactionID == null) {
throw new InvalidArgumentException("Proposals with missing transaction ID");
}
proposalResponsePayload = sdkProposalResponse.getProposalResponse().getPayload();
if (proposalResponsePayload == null) {
throw new InvalidArgumentException("Proposals with missing payload.");
}
transactionContext = sdkProposalResponse.getTransactionContext();
if (transactionContext == null) {
throw new InvalidArgumentException("Proposals with missing transaction context.");
}
} else {
final String transactionID = sdkProposalResponse.getTransactionID();
if (transactionID == null) {
throw new InvalidArgumentException("Proposals with missing transaction id.");
}
if (!proposalTransactionID.equals(transactionID)) {
throw new InvalidArgumentException(format("Proposals with different transaction IDs %s, and %s", proposalTransactionID, transactionID));
}
}
}
TransactionBuilder transactionBuilder = TransactionBuilder.newBuilder();
Payload transactionPayload = transactionBuilder
.chaincodeProposal(proposal)
.endorsements(ed)
.proposalResponsePayload(proposalResponsePayload).build();
Envelope transactionEnvelope = createTransactionEnvelope(transactionPayload, transactionContext);
NOfEvents nOfEvents = transactionOptions.nOfEvents;
if (nOfEvents == null) {
nOfEvents = NOfEvents.createNofEvents();
Collection eventingPeers = getEventingPeers();
boolean anyAdded = false;
if (!eventingPeers.isEmpty()) {
anyAdded = true;
nOfEvents.addPeers(eventingPeers);
}
Collection eventHubs = getEventHubs();
if (!eventHubs.isEmpty()) {
anyAdded = true;
nOfEvents.addEventHubs(getEventHubs());
}
if (!anyAdded) {
nOfEvents = NOfEvents.createNoEvents();
}
} else if (nOfEvents != NOfEvents.nofNoEvents) {
StringBuilder issues = new StringBuilder(100);
Collection eventingPeers = getEventingPeers();
nOfEvents.unSeenPeers().forEach(peer -> {
if (peer.getChannel() != this) {
issues.append(format("Peer %s added to NOFEvents does not belong this channel. ", peer.getName()));
} else if (!eventingPeers.contains(peer)) {
issues.append(format("Peer %s added to NOFEvents is not a eventing Peer in this channel. ", peer.getName()));
}
});
nOfEvents.unSeenEventHubs().forEach(eventHub -> {
if (!eventHubs.contains(eventHub)) {
issues.append(format("Eventhub %s added to NOFEvents does not belong this channel. ", eventHub.getName()));
}
});
if (nOfEvents.unSeenEventHubs().isEmpty() && nOfEvents.unSeenPeers().isEmpty()) {
issues.append("NofEvents had no Eventhubs added or Peer eventing services.");
}
String foundIssues = issues.toString();
if (!foundIssues.isEmpty()) {
throw new InvalidArgumentException(foundIssues);
}
}
final boolean replyonly = nOfEvents == NOfEvents.nofNoEvents || (getEventHubs().isEmpty() && getEventingPeers().isEmpty());
CompletableFuture sret;
if (replyonly) { //If there are no eventhubs to complete the future, complete it
// immediately but give no transaction event
logger.debug(format("Completing transaction id %s immediately no event hubs or peer eventing services found in channel %s.", proposalTransactionID, name));
sret = new CompletableFuture<>();
} else {
sret = registerTxListener(proposalTransactionID, nOfEvents, transactionOptions.failFast);
}
logger.debug(format("Channel %s sending transaction to orderer(s) with TxID %s ", name, proposalTransactionID));
boolean success = false;
Exception lException = null; // Save last exception to report to user .. others are just logged.
BroadcastResponse resp = null;
Orderer failed = null;
for (Orderer orderer : shuffeledOrderers) {
if (failed != null) {
logger.warn(format("Channel %s %s failed. Now trying %s.", name, failed, orderer));
}
failed = orderer;
try {
if (null != diagnosticFileDumper) {
logger.trace(format("Sending to channel %s, orderer: %s, transaction: %s", name, orderer.getName(),
diagnosticFileDumper.createDiagnosticProtobufFile(transactionEnvelope.toByteArray())));
}
resp = orderer.sendTransaction(transactionEnvelope);
lException = null; // no longer last exception .. maybe just failed.
if (resp.getStatus() == Status.SUCCESS) {
success = true;
break;
} else {
logger.warn(format("Channel %s %s failed. Status returned %s", name, orderer, getRespData(resp)));
}
} catch (Exception e) {
String emsg = format("Channel %s unsuccessful sendTransaction to orderer %s (%s)",
name, orderer.getName(), orderer.getUrl());
if (resp != null) {
emsg = format("Channel %s unsuccessful sendTransaction to orderer %s (%s). %s",
name, orderer.getName(), orderer.getUrl(), getRespData(resp));
}
logger.error(emsg);
lException = new Exception(emsg, e);
}
}
if (success) {
logger.debug(format("Channel %s successful sent to Orderer transaction id: %s",
name, proposalTransactionID));
if (replyonly) {
sret.complete(null); // just say we're done.
}
return sret;
} else {
String emsg = format("Channel %s failed to place transaction %s on Orderer. Cause: UNSUCCESSFUL. %s",
name, proposalTransactionID, getRespData(resp));
unregisterTxListener(proposalTransactionID);
CompletableFuture ret = new CompletableFuture<>();
ret.completeExceptionally(lException != null ? new Exception(emsg, lException) : new Exception(emsg));
return ret;
}
} catch (Exception e) {
CompletableFuture future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
/**
* Build response details
*
* @param resp
* @return
*/
private String getRespData(BroadcastResponse resp) {
StringBuilder respdata = new StringBuilder(400);
if (resp != null) {
Status status = resp.getStatus();
if (null != status) {
respdata.append(status.name());
respdata.append("-");
respdata.append(status.getNumber());
}
String info = resp.getInfo();
if (null != info && !info.isEmpty()) {
if (respdata.length() > 0) {
respdata.append(", ");
}
respdata.append("Additional information: ").append(info);
}
}
return respdata.toString();
}
private Envelope createTransactionEnvelope(Payload transactionPayload, TransactionContext transactionContext) throws CryptoException, InvalidArgumentException {
return Envelope.newBuilder()
.setPayload(transactionPayload.toByteString())
.setSignature(ByteString.copyFrom(transactionContext.sign(transactionPayload.toByteArray())))
.build();
}
byte[] getChannelConfigurationSignature(ChannelConfiguration channelConfiguration, User signer) throws InvalidArgumentException {
userContextCheck(signer);
if (null == channelConfiguration) {
throw new InvalidArgumentException("channelConfiguration is null");
}
try {
Envelope ccEnvelope = Envelope.parseFrom(channelConfiguration.getChannelConfigurationAsBytes());
final Payload ccPayload = Payload.parseFrom(ccEnvelope.getPayload());
TransactionContext transactionContext = getTransactionContext(signer);
final ConfigUpdateEnvelope configUpdateEnv = ConfigUpdateEnvelope.parseFrom(ccPayload.getData());
final ByteString configUpdate = configUpdateEnv.getConfigUpdate();
ByteString sigHeaderByteString = getSignatureHeaderAsByteString(signer, transactionContext);
ByteString signatureByteSting = transactionContext.signByteStrings(new User[] {signer},
sigHeaderByteString, configUpdate)[0];
return ConfigSignature.newBuilder()
.setSignatureHeader(sigHeaderByteString)
.setSignature(signatureByteSting)
.build().toByteArray();
} catch (Exception e) {
throw new InvalidArgumentException(e);
} finally {
logger.debug("finally done");
}
}
/**
* Register a block listener.
*
* @param listener function with single argument with type {@link BlockEvent}
* @return The handle of the registered block listener.
* @throws InvalidArgumentException if the channel is shutdown.
*/
public String registerBlockListener(BlockListener listener) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
return new BL(listener).getHandle();
}
/**
* Unregister a block listener.
*
* @param handle of Block listener to remove.
* @return false if not found.
* @throws InvalidArgumentException if the channel is shutdown or invalid arguments.
*/
public boolean unregisterBlockListener(String handle) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
checkHandle(BLOCK_LISTENER_TAG, handle);
synchronized (blockListeners) {
return null != blockListeners.remove(handle);
}
}
////////// Transaction monitoring /////////////////////////////
private void startEventQue() {
if (eventQueueThread != null) {
return;
}
client.getExecutorService().execute(() -> {
eventQueueThread = Thread.currentThread();
while (!shutdown) {
if (!initialized) {
try {
logger.debug("not intialized:" + initialized);
Thread.sleep(1);
} catch (InterruptedException e) {
logger.warn(e);
}
continue; //wait on sending events till the channel is initialized.
}
final BlockEvent blockEvent;
try {
blockEvent = channelEventQue.getNextEvent();
} catch (EventHubException e) {
if (!shutdown) {
logger.error(e);
}
continue;
}
if (blockEvent == null) {
logger.warn("GOT null block event.");
continue;
}
try {
final String blockchainID = blockEvent.getChannelId();
final String from =
format("Channel %s eventqueue got block event with block number: %d for channel: %s, from %s",
name, blockEvent.getBlockNumber(), blockchainID, blockEvent.getPeer() != null ? ("" + blockEvent.getPeer()) :
("" + blockEvent.getEventHub()));
logger.trace(from);
if (!Objects.equals(name, blockchainID)) {
logger.warn(format("Channel %s eventqueue got block event NOT FOR ME channelId %s from %s", name, blockchainID, from));
continue; // not targeted for this channel
}
final ArrayList blcopy = new ArrayList<>(blockListeners.size() + 3);
synchronized (blockListeners) {
blcopy.addAll(blockListeners.values());
}
for (BL l : blcopy) {
try {
logger.trace(format("Sending block event '%s' to block listener %s", from, l.handle));
client.getExecutorService().execute(() -> l.listener.received(blockEvent));
} catch (Throwable e) { //Don't let one register stop rest.
logger.error(format("Error calling block listener %s on channel: %s event: %s ", l.handle, name, from), e);
}
}
} catch (Exception e) {
logger.error("Unable to parse event", e);
logger.debug("event:\n)");
logger.debug(blockEvent.toString());
}
}
logger.info(format("Channel %s eventThread shutting down. shutdown: %b thread: %s ", name, shutdown, Thread.currentThread().getName()));
});
}
/**
* Own block listener to manage transactions.
*
* @return
*/
private String registerTransactionListenerProcessor() throws InvalidArgumentException {
logger.debug(format("Channel %s registerTransactionListenerProcessor starting", name));
// Transaction listener is internal Block listener for transactions
return registerBlockListener(blockEvent -> {
HFClient lclient = client;
if (null == lclient || shutdown) { //can happen if were not quite shutdown
return;
}
final String source = blockEvent.getPeer() != null ? blockEvent.getPeer().toString() :
(blockEvent.getEventHub() != null ? blockEvent.getEventHub().toString() : "not peer or eventhub!");
logger.debug(format("is peer %b, is filtered: %b", blockEvent.getPeer() != null, blockEvent.isFiltered()));
final Iterable transactionEvents = blockEvent.getTransactionEvents();
if (transactionEvents == null || !transactionEvents.iterator().hasNext()) {
// no transactions today we can assume it was a config or update block.
if (isLaterBlock(blockEvent.getBlockNumber())) {
ServiceDiscovery lserviceDiscovery = serviceDiscovery;
if (null != lserviceDiscovery) {
client.getExecutorService().execute(() -> lserviceDiscovery.fullNetworkDiscovery(true));
}
} else {
lclient.getExecutorService().execute(() -> {
try {
if (!shutdown) {
loadCACertificates(true);
}
} catch (Exception e) {
logger.warn(format("Channel %s failed to load certificates for an update", name), e);
}
});
}
return;
}
if (txListeners.isEmpty() || shutdown) {
return;
}
for (TransactionEvent transactionEvent : blockEvent.getTransactionEvents()) {
logger.debug(format("Channel %s got event from %s for transaction %s in block number: %d", name,
source, transactionEvent.getTransactionID(), blockEvent.getBlockNumber()));
List txL = new ArrayList<>(txListeners.size() + 2);
synchronized (txListeners) {
LinkedList list = txListeners.get(transactionEvent.getTransactionID());
if (null != list) {
txL.addAll(list);
}
}
for (TL l : txL) {
try {
// only if we get events from each eventhub on the channel fire the transactions event.
// if (getEventHubs().containsAll(l.eventReceived(transactionEvent.getEventHub()))) {
if (shutdown) {
break;
}
if (l.eventReceived(transactionEvent)) {
l.fire(transactionEvent);
}
} catch (Throwable e) {
logger.error(e); // Don't let one register stop rest.
}
}
}
});
}
private volatile long lastBlock = -1L;
private synchronized boolean isLaterBlock(final long blockno) {
if (blockno > lastBlock) {
lastBlock = blockno;
return true;
}
return false;
}
void runSweeper() {
if (shutdown || DELTA_SWEEP < 1) {
return;
}
if (sweeper == null) {
sweeper = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}).scheduleAtFixedRate(() -> {
try {
if (txListeners != null) {
synchronized (txListeners) {
for (Iterator>> it = txListeners.entrySet().iterator(); it.hasNext();
) {
Map.Entry> es = it.next();
LinkedList tlLinkedList = es.getValue();
tlLinkedList.removeIf(TL::sweepMe);
if (tlLinkedList.isEmpty()) {
it.remove();
}
}
}
}
} catch (Exception e) {
logger.warn("Sweeper got error:" + e.getMessage(), e);
}
}, 0, DELTA_SWEEP, TimeUnit.MILLISECONDS);
}
}
/**
* Register a transactionId that to get notification on when the event is seen in the block chain.
*
* @param txid
* @param nOfEvents
* @return
*/
private CompletableFuture registerTxListener(String txid, NOfEvents nOfEvents, boolean failFast) {
CompletableFuture future = new CompletableFuture<>();
new TL(txid, future, nOfEvents, failFast);
return future;
}
/**
* Unregister a transactionId
*
* @param txid
*/
private void unregisterTxListener(String txid) {
synchronized (txListeners) {
txListeners.remove(txid);
}
}
/**
* Register a chaincode event listener. Both chaincodeId pattern AND eventName pattern must match to invoke
* the chaincodeEventListener
*
* @param chaincodeId Java pattern for chaincode identifier also know as chaincode name. If ma
* @param eventName Java pattern to match the event name.
* @param chaincodeEventListener The listener to be invoked if both chaincodeId and eventName pattern matches.
* @return Handle to be used to unregister the event listener {@link #unregisterChaincodeEventListener(String)}
* @throws InvalidArgumentException
*/
public String registerChaincodeEventListener(Pattern chaincodeId, Pattern eventName, ChaincodeEventListener chaincodeEventListener) throws InvalidArgumentException {
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
if (chaincodeId == null) {
throw new InvalidArgumentException("The chaincodeId argument may not be null.");
}
if (eventName == null) {
throw new InvalidArgumentException("The eventName argument may not be null.");
}
if (chaincodeEventListener == null) {
throw new InvalidArgumentException("The chaincodeEventListener argument may not be null.");
}
ChaincodeEventListenerEntry chaincodeEventListenerEntry = new ChaincodeEventListenerEntry(chaincodeId, eventName, chaincodeEventListener);
synchronized (this) {
if (null == blh) {
blh = registerChaincodeListenerProcessor();
}
}
return chaincodeEventListenerEntry.handle;
}
/**
* Unregister an existing chaincode event listener.
*
* @param handle Chaincode event listener handle to be unregistered.
* @return True if the chaincode handler was found and removed.
* @throws InvalidArgumentException
*/
public boolean unregisterChaincodeEventListener(String handle) throws InvalidArgumentException {
boolean ret;
if (shutdown) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", name));
}
checkHandle(CHAINCODE_EVENTS_TAG, handle);
synchronized (chainCodeListeners) {
ret = null != chainCodeListeners.remove(handle);
}
synchronized (this) {
if (null != blh && chainCodeListeners.isEmpty()) {
unregisterBlockListener(blh);
blh = null;
}
}
return ret;
}
////////////////////////////////////////////////////////////////////////
//////////////// Chaincode Events.. //////////////////////////////////
private String registerChaincodeListenerProcessor() throws InvalidArgumentException {
logger.debug(format("Channel %s registerChaincodeListenerProcessor starting", name));
// Chaincode event listener is internal Block listener for chaincode events.
return registerBlockListener(blockEvent -> {
if (chainCodeListeners.isEmpty()) {
return;
}
LinkedList chaincodeEvents = new LinkedList<>();
//Find the chaincode events in the transactions.
for (TransactionEvent transactionEvent : blockEvent.getTransactionEvents()) {
logger.debug(format("Channel %s got event for transaction %s ", name, transactionEvent.getTransactionID()));
for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo info : transactionEvent.getTransactionActionInfos()) {
ChaincodeEvent event = info.getEvent();
if (null != event) {
chaincodeEvents.add(event);
}
}
}
if (!chaincodeEvents.isEmpty()) {
class MatchPair {
final ChaincodeEventListenerEntry eventListener;
final ChaincodeEvent event;
MatchPair(ChaincodeEventListenerEntry eventListener, ChaincodeEvent event) {
this.eventListener = eventListener;
this.event = event;
}
}
List matches = new LinkedList<>(); //Find matches.
synchronized (chainCodeListeners) {
for (ChaincodeEventListenerEntry chaincodeEventListenerEntry : chainCodeListeners.values()) {
for (ChaincodeEvent chaincodeEvent : chaincodeEvents) {
if (chaincodeEventListenerEntry.isMatch(chaincodeEvent)) {
matches.add(new MatchPair(chaincodeEventListenerEntry, chaincodeEvent));
}
}
}
}
//fire events
for (MatchPair match : matches) {
ChaincodeEventListenerEntry chaincodeEventListenerEntry = match.eventListener;
ChaincodeEvent ce = match.event;
chaincodeEventListenerEntry.fire(blockEvent, ce);
}
}
});
}
/**
* Shutdown the channel with all resources released.
*
* @param force force immediate shutdown.
*/
public synchronized void shutdown(boolean force) {
if (shutdown) {
return;
}
String ltransactionListenerProcessorHandle = transactionListenerProcessorHandle;
transactionListenerProcessorHandle = null;
if (null != ltransactionListenerProcessorHandle) {
try {
unregisterBlockListener(ltransactionListenerProcessorHandle);
} catch (Exception e) {
logger.error(format("Shutting down channel %s transactionListenerProcessorHandle", name), e);
}
}
String lchaincodeEventUpgradeListenerHandle = chaincodeEventUpgradeListenerHandle;
chaincodeEventUpgradeListenerHandle = null;
if (null != lchaincodeEventUpgradeListenerHandle) {
try {
unregisterChaincodeEventListener(lchaincodeEventUpgradeListenerHandle);
} catch (Exception e) {
logger.error(format("Shutting down channel %s chaincodeEventUpgradeListenr", name), e);
}
}
initialized = false;
shutdown = true;
final ServiceDiscovery lserviceDiscovery = serviceDiscovery;
serviceDiscovery = null;
if (null != lserviceDiscovery) {
lserviceDiscovery.shutdown();
}
if (chainCodeListeners != null) {
chainCodeListeners.clear();
}
if (blockListeners != null) {
blockListeners.clear();
}
if (client != null) {
client.removeChannel(this);
}
client = null;
for (EventHub eh : eventHubs) {
try {
eh.shutdown();
} catch (Exception e) {
// Best effort.
}
}
eventHubs.clear();
for (Peer peer : new ArrayList<>(getPeers())) {
try {
removePeerInternal(peer);
peer.shutdown(force);
} catch (Exception e) {
// Best effort.
}
}
peers.clear(); // make sure.
peerEndpointMap.clear();
ordererEndpointMap.clear();
//Make sure
for (Set peerRoleSet : peerRoleSetMap.values()) {
peerRoleSet.clear();
}
for (Orderer orderer : getOrderers()) {
orderer.shutdown(force);
}
orderers.clear();
if (null != eventQueueThread) {
eventQueueThread.interrupt();
eventQueueThread = null;
}
ScheduledFuture lsweeper = sweeper;
sweeper = null;
if (null != lsweeper) {
lsweeper.cancel(true);
}
}
/**
* Serialize channel to a file using Java serialization.
* Deserialized channel will NOT be in an initialized state.
*
* @param file file
* @throws IOException
* @throws InvalidArgumentException
*/
public void serializeChannel(File file) throws IOException, InvalidArgumentException {
if (null == file) {
throw new InvalidArgumentException("File parameter may not be null");
}
Files.write(Paths.get(file.getAbsolutePath()), serializeChannel(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
}
/**
* Serialize channel to a byte array using Java serialization.
* Deserialized channel will NOT be in an initialized state.
*
* @throws InvalidArgumentException
* @throws IOException
*/
public byte[] serializeChannel() throws IOException, InvalidArgumentException {
if (isShutdown()) {
throw new InvalidArgumentException(format("Channel %s has been shutdown.", getName()));
}
ObjectOutputStream out = null;
try {
ByteArrayOutputStream bai = new ByteArrayOutputStream();
out = new ObjectOutputStream(bai);
out.writeObject(this);
out.flush();
return bai.toByteArray();
} finally {
if (null != out) {
try {
out.close();
} catch (IOException e) {
logger.error(e); // best effort.
}
}
}
}
@Override
protected void finalize() throws Throwable {
shutdown(true);
super.finalize();
}
/**
* Options for the peer.
* These options are channel based.
*/
public static class PeerOptions implements Cloneable, Serializable {
private static final long serialVersionUID = -6906605662806520793L;
protected EnumSet peerRoles;
protected Boolean newest = true;
protected Long startEvents;
protected Long stopEvents = Long.MAX_VALUE;
protected boolean registerEventsForFilteredBlocks = false;
@Override
public String toString() {
StringBuilder sb = new StringBuilder(1000);
sb.append("PeerOptions( " + format("newest: %s, startEvents: %s, stopEvents: %s, registerEventsForFilteredBlocks: %s", "" + newest, "" + startEvents, "" + stopEvents, registerEventsForFilteredBlocks));
if (peerRoles != null && !peerRoles.isEmpty()) {
sb.append(", PeerRoles:[");
String sep = "";
for (PeerRole peerRole : peerRoles) {
sb.append(sep).append(peerRole.getPropertyName());
sep = " ,";
}
sb.append("]");
}
sb.append(")");
return sb.toString();
}
/**
* Is the peer eventing service registered for filtered blocks
*
* @return true if filtered blocks will be returned by the peer eventing service.
*/
public boolean isRegisterEventsForFilteredBlocks() {
return registerEventsForFilteredBlocks;
}
/**
* Register the peer eventing services to return filtered blocks.
*
* @return the PeerOptions instance.
*/
public PeerOptions registerEventsForFilteredBlocks() {
registerEventsForFilteredBlocks = true;
return this;
}
/**
* Register the peer eventing services to return full event blocks.
*
* @return the PeerOptions instance.
*/
public PeerOptions registerEventsForBlocks() {
registerEventsForFilteredBlocks = false;
return this;
}
/**
* Get newest block on startup of peer eventing service.
*
* @return
*/
public Boolean getNewest() {
return newest;
}
/**
* The block number to start getting events from on start up of the peer eventing service..
*
* @return the start number
*/
public Long getStartEvents() {
return startEvents;
}
/**
* The stopping block number when the peer eventing service will stop sending blocks.
*
* @return the stop block number.
*/
public Long getStopEvents() {
return stopEvents;
}
protected PeerOptions() {
}
/**
* Create an instance of PeerOptions.
*
* @return the PeerOptions instance.
*/
public static PeerOptions createPeerOptions() {
return new PeerOptions();
}
/**
* Return the roles the peer has.
*
* @return the roles {@link PeerRole}
*/
public EnumSet getPeerRoles() {
if (peerRoles == null) {
peerRoles = EnumSet.complementOf(EnumSet.of(PeerRole.SERVICE_DISCOVERY));
}
return peerRoles;
}
/**
* Set the roles this peer will have on the chain it will added or joined.
*
* @param peerRoles {@link PeerRole}
* @return This PeerOptions.
*/
public PeerOptions setPeerRoles(EnumSet peerRoles) {
this.peerRoles = peerRoles;
return this;
}
/**
* Add to the roles this peer will have on the chain it will added or joined.
*
* @param peerRole see {@link PeerRole}
* @return This PeerOptions.
*/
public PeerOptions addPeerRole(PeerRole peerRole) {
if (peerRoles == null) {
peerRoles = EnumSet.noneOf(PeerRole.class);
}
peerRoles.add(peerRole);
return this;
}
/**
* Set the block number the eventing peer will start relieving events.
*
* @param start The staring block number.
* @return This PeerOptions.
*/
public PeerOptions startEvents(long start) {
startEvents = start;
newest = null;
return this;
}
/**
* This is the default. It will start retrieving events with the newest. Note this is not the
* next block that is added to the chain but the current block on the chain.
*
* @return This PeerOptions.
*/
public PeerOptions startEventsNewest() {
startEvents = null;
newest = true;
return this;
}
/**
* The block number to stop sending events.
*
* @param stop the number to stop sending events.
* @return This PeerOptions.
*/
public PeerOptions stopEvents(long stop) {
stopEvents = stop;
return this;
}
/**
* Clone.
*
* @return return a duplicate of this instance.
*/
public PeerOptions clone() {
try {
return (PeerOptions) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
/**
* MSPs
*/
class MSP {
final String orgName;
final MspConfig.FabricMSPConfig fabricMSPConfig;
byte[][] adminCerts;
byte[][] rootCerts;
byte[][] intermediateCerts;
MSP(String orgName, MspConfig.FabricMSPConfig fabricMSPConfig) {
this.orgName = orgName;
this.fabricMSPConfig = fabricMSPConfig;
}
/**
* Known as the MSPID internally
*
* @return
*/
String getID() {
return fabricMSPConfig.getName();
}
/**
* AdminCerts
*
* @return array of admin certs in PEM bytes format.
*/
byte[][] getAdminCerts() {
if (null == adminCerts) {
adminCerts = new byte[fabricMSPConfig.getAdminsList().size()][];
int i = 0;
for (ByteString cert : fabricMSPConfig.getAdminsList()) {
adminCerts[i++] = cert.toByteArray();
}
}
return adminCerts;
}
/**
* RootCerts
*
* @return array of admin certs in PEM bytes format.
*/
byte[][] getRootCerts() {
if (null == rootCerts) {
rootCerts = new byte[fabricMSPConfig.getRootCertsList().size()][];
int i = 0;
for (ByteString cert : fabricMSPConfig.getRootCertsList()) {
rootCerts[i++] = cert.toByteArray();
}
}
return rootCerts;
}
/**
* IntermediateCerts
*
* @return array of intermediate certs in PEM bytes format.
*/
byte[][] getIntermediateCerts() {
if (null == intermediateCerts) {
intermediateCerts = new byte[fabricMSPConfig.getIntermediateCertsList().size()][];
int i = 0;
for (ByteString cert : fabricMSPConfig.getIntermediateCertsList()) {
intermediateCerts[i++] = cert.toByteArray();
}
}
return intermediateCerts;
}
}
class ChannelEventQue {
private final BlockingQueue events = new LinkedBlockingQueue<>(); //Thread safe
private Throwable eventException;
void eventError(Throwable t) {
eventException = t;
}
boolean addBEvent(BlockEvent event) {
if (shutdown) {
return false;
}
//For now just support blocks --- other types are also reported as blocks.
if (!event.isBlockEvent()) {
return false;
}
// May be fed by multiple eventhubs but BlockingQueue.add() is thread-safe
events.add(event);
return true;
}
BlockEvent getNextEvent() throws EventHubException {
if (shutdown) {
throw new EventHubException(format("Channel %s has been shutdown", name));
}
BlockEvent ret = null;
if (eventException != null) {
throw new EventHubException(eventException);
}
try {
ret = events.take();
} catch (InterruptedException e) {
if (shutdown) {
throw new EventHubException(format("channel %s is shutdown", name), e);
} else {
logger.warn(e);
if (eventException != null) {
EventHubException eve = new EventHubException(e);
logger.error(eve.getMessage(), eve);
throw eve;
}
}
}
if (eventException != null) {
throw new EventHubException(eventException);
}
if (shutdown) {
throw new EventHubException(format("Channel %s has been shutdown.", name));
}
return ret;
}
}
class BL {
final BlockListener listener;
final String handle;
BL(BlockListener listener) {
handle = BLOCK_LISTENER_TAG + Utils.generateUUID() + BLOCK_LISTENER_TAG;
logger.debug(format("Channel %s blockListener %s starting", name, handle));
this.listener = listener;
synchronized (blockListeners) {
blockListeners.put(handle, this);
}
}
public String getHandle() {
return handle;
}
}
private class TL {
final String txID;
final long createTime = System.currentTimeMillis();
final AtomicBoolean fired = new AtomicBoolean(false);
final CompletableFuture future;
final boolean failFast;
final Set peers;
final Set eventHubs;
private final NOfEvents nOfEvents;
long sweepTime = System.currentTimeMillis() + (long) (DELTA_SWEEP * 1.5);
TL(String txID, CompletableFuture future, NOfEvents nOfEvents, boolean failFast) {
this.txID = txID;
this.future = future;
this.nOfEvents = new NOfEvents(nOfEvents);
peers = new HashSet<>(nOfEvents.unSeenPeers());
eventHubs = new HashSet<>(nOfEvents.unSeenEventHubs());
this.failFast = failFast;
addListener();
}
/**
* Record transactions event.
*
* @param transactionEvent
* @return True if transactions have been seen on all eventing peers and eventhubs.
*/
boolean eventReceived(TransactionEvent transactionEvent) {
sweepTime = System.currentTimeMillis() + DELTA_SWEEP; //seen activity keep it active.
final Peer peer = transactionEvent.getPeer();
final EventHub eventHub = transactionEvent.getEventHub();
if (peer != null && !peers.contains(peer)) {
return false;
}
if (eventHub != null && !eventHubs.contains(eventHub)) {
return false;
}
if (failFast && !transactionEvent.isValid()) {
return true;
}
if (peer != null) {
nOfEvents.seen(peer);
logger.debug(format("Channel %s seen transaction event %s for peer %s", name, txID, peer.toString()));
} else if (null != eventHub) {
logger.debug(format("Channel %s seen transaction event %s for eventHub %s", name, txID, eventHub.toString()));
nOfEvents.seen(eventHub);
} else {
logger.error(format("Channel %s seen transaction event %s with no associated peer or eventhub", name, txID));
}
boolean isEmpty;
synchronized (this) {
isEmpty = nOfEvents.ready;
}
return isEmpty;
}
private void addListener() {
runSweeper();
synchronized (txListeners) {
LinkedList tl = txListeners.computeIfAbsent(txID, k -> new LinkedList<>());
tl.add(this);
}
}
boolean sweepMe() { // Sweeps DO NOT fire future. user needs to put timeout on their futures for timeouts.
final boolean ret = sweepTime < System.currentTimeMillis() || fired.get() || future.isDone();
if (IS_WARN_LEVEL && ret) {
StringBuilder sb = new StringBuilder(10000);
sb.append("Non reporting event hubs:");
String sep = "";
for (EventHub eh : nOfEvents.unSeenEventHubs()) {
sb.append(sep).append(eh.toString())
.append(" status: ")
.append(eh.getStatus());
sep = ", ";
}
if (sb.length() != 0) {
sb.append(". ");
}
sep = " Non reporting peers: ";
for (Peer peer : nOfEvents.unSeenPeers()) {
sb.append(sep).append(peer.toString()).append(" status:")
.append(peer.getEventingStatus());
sep = ", ";
}
logger.warn(format("Force removing transaction listener after %d ms for transaction %s. %s" +
". sweep timeout: %b, fired: %b, future done:%b",
System.currentTimeMillis() - createTime, txID, sb.toString(),
sweepTime < System.currentTimeMillis(), fired.get(), future.isDone()));
}
return ret;
}
void fire(BlockEvent.TransactionEvent transactionEvent) {
if (fired.getAndSet(true)) {
return;
}
synchronized (txListeners) {
LinkedList l = txListeners.get(txID);
if (null != l) {
l.removeFirstOccurrence(this);
if (l.size() == 0) {
txListeners.remove(txID);
}
}
}
if (future.isDone()) {
fired.set(true);
return;
}
if (transactionEvent.isValid()) {
logger.debug(format("Completing future for channel %s and transaction id: %s", name, txID));
client.getExecutorService().execute(() -> future.complete(transactionEvent));
} else {
logger.debug(format("Completing future as exception for channel %s and transaction id: %s, validation code: %02X",
name, txID, transactionEvent.getValidationCode()));
client.getExecutorService().execute(() -> future.completeExceptionally(
new TransactionEventException(format("Received invalid transaction event. Transaction ID %s status %s",
transactionEvent.getTransactionID(),
transactionEvent.getValidationCode()),
transactionEvent)));
}
}
}
private class ChaincodeEventListenerEntry {
private final Pattern chaincodeIdPattern;
private final Pattern eventNamePattern;
private final ChaincodeEventListener chaincodeEventListener;
private final String handle;
ChaincodeEventListenerEntry(Pattern chaincodeIdPattern, Pattern eventNamePattern, ChaincodeEventListener chaincodeEventListener) {
this.chaincodeIdPattern = chaincodeIdPattern;
this.eventNamePattern = eventNamePattern;
this.chaincodeEventListener = chaincodeEventListener;
this.handle = CHAINCODE_EVENTS_TAG + Utils.generateUUID() + CHAINCODE_EVENTS_TAG;
synchronized (chainCodeListeners) {
chainCodeListeners.put(handle, this);
}
}
boolean isMatch(ChaincodeEvent chaincodeEvent) {
return chaincodeIdPattern.matcher(chaincodeEvent.getChaincodeId()).matches() && eventNamePattern.matcher(chaincodeEvent.getEventName()).matches();
}
void fire(BlockEvent blockEvent, ChaincodeEvent ce) {
client.getExecutorService().execute(() -> chaincodeEventListener.received(handle, blockEvent, ce));
}
}
}