All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hyperledger.fabric.sdk.Channel Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 *  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 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.*;
import org.hyperledger.fabric.protos.common.Configtx.*;
import org.hyperledger.fabric.protos.common.Ledger;
import org.hyperledger.fabric.protos.msp.MspConfig;
import org.hyperledger.fabric.protos.orderer.Ab;
import org.hyperledger.fabric.protos.orderer.Ab.*;
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.PeerEvents.Event.EventCase;
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.exception.*;
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.transaction.*;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import static java.lang.String.format;
import static org.hyperledger.fabric.sdk.User.userContextCheck;
import static org.hyperledger.fabric.sdk.helper.Utils.isNullOrEmpty;
import static org.hyperledger.fabric.sdk.transaction.ProtoUtils.*;

/**
 * The class representing a channel with which the client SDK interacts.
 * 

*/ public class Channel { private static final Log logger = LogFactory.getLog(Channel.class); private static final boolean IS_DEBUG_LEVEL = logger.isDebugEnabled(); private static final boolean IS_TRACE_LEVEL = logger.isTraceEnabled(); private static final Config config = Config.getConfig(); 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(); // Name of the channel is only meaningful to the client private final String name; // The peers on this channel to which the client can connect private final Collection peers = new Vector<>(); // Temporary variables to control how long to wait for deploy and invoke to complete before // emitting events. This will be removed when the SDK is able to receive events from the private int deployWaitTime = 20; private int transactionWaitTime = 5; // contains the anchor peers parsed from the channel's configBlock // private Set anchorPeers; // The crypto primitives object // private CryptoSuite cryptoSuite; private final Collection orderers = new LinkedList<>(); HFClient client; private boolean initialized = false; private boolean shutdown = false; /** * Get all Event Hubs on this channel. * * @return Event Hubs */ public Collection getEventHubs() { return Collections.unmodifiableCollection(eventHubs); } private final Collection eventHubs = new LinkedList<>(); private ExecutorService executorService; private Block genesisBlock; private final boolean systemChannel; 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); } } /** * 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); 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", name, duration)); //waited long enough .. currentLastConfigIndex = startLastConfigIndex; // 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); } } } } 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); 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 = ""; 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 = ""; 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(); } /** * 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); } public boolean isInitialized() { return initialized; } 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; this.executorService = client.getExecutorService(); logger.debug(format("Creating channel: %s, client context %s", isSystemChannel() ? "SYSTEM_CHANNEL" : name, client.getUserContext().getName())); } /** * 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 { 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."); } peer.setChannel(this); peers.add(peer); return this; } public Channel joinPeer(Peer peer) 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())); } 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(getRandomOrderer()); 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); //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.getName(), name)); } else { peers.remove(peer); peer.unsetChannel(); throw new ProposalException(format("Join peer to channel %s failed. Status %s, details: %s", name, pro.getStatus().toString(), pro.getMessage())); } } catch (ProposalException e) { peers.remove(peer); peer.unsetChannel(); logger.error(e); throw e; } catch (Exception e) { peers.remove(peer); peer.unsetChannel(); logger.error(e); throw new ProposalException(e.getMessage(), e); } return this; } /** * 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 orderer%s, url: %s", name, orderer.getName(), orderer.getUrl())); orderer.setChannel(this); orderers.add(orderer); return this; } /** * 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, url: %s", name, eventHub.getName(), eventHub.getUrl())); eventHub.setChannel(this); eventHub.setEventQue(channelEventQue); eventHubs.add(eventHub); return this; } /** * Get the peers for this channel. * * @return the peers. */ public Collection getPeers() { return Collections.unmodifiableCollection(peers); } /** * Get the deploy wait time in seconds. * * @return number of seconds. */ public int getDeployWaitTime() { return deployWaitTime; } /** * Set the deploy wait time in seconds. * * @param waitTime Deploy wait time */ public void setDeployWaitTime(int waitTime) { this.deployWaitTime = waitTime; } /** * Get the transaction wait time in seconds * * @return transaction wait time */ public int getTransactionWaitTime() { return this.transactionWaitTime; } /** * Set the transaction wait time in seconds. * * @param waitTime Invoke wait time */ public void setTransactionWaitTime(int waitTime) { logger.trace("setTransactionWaitTime is:" + waitTime); transactionWaitTime = waitTime; } /** * 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 (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()); try { parseConfigBlock(); // Parse config block for this channel to get it's information. loadCACertificates(); // put all MSP certs into cryptoSuite startEventQue(); //Run the event for event messages from event hubs. logger.debug(format("Eventque started %s", "" + eventQueueThread)); for (EventHub eh : eventHubs) { //Connect all event hubs eh.connect(getTransactionContext()); } logger.debug(format("%d eventhubs initialized", getEventHubs().size())); registerTransactionListenerProcessor(); //Manage transactions. logger.debug(format("Channel %s registerTransactionListenerProcessor completed", name)); this.initialized = true; logger.debug(format("Channel %s initialized", name)); return this; } catch (TransactionException e) { logger.error(e.getMessage(), e); throw e; } catch (Exception e) { TransactionException exp = new TransactionException(e); logger.error(exp.getMessage(), exp); throw exp; } } /** * 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 */ private void loadCACertificates() throws InvalidArgumentException, CryptoException { logger.debug(format("Channel %s loadCACertificates", name)); if (msps == null) { throw new InvalidArgumentException("Unable to load CA certificates. Channel " + name + " does not have any MSPs."); } List certList; for (MSP msp : msps.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; } private Map msps = new HashMap<>(); boolean isSystemChannel() { return systemChannel; } public boolean isShutdown() { return shutdown; } 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"); } } /** * 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; } } // /** // * Anchor holds the info for the anchor peers as parsed from the configuration block // */ // class Anchor { // public String hostName; // public int port; // // Anchor(String hostName, int port) throws InvalidArgumentException { // this.hostName = hostName; // this.port = port; // } // } protected void parseConfigBlock() throws TransactionException { try { final Block configBlock = getConfigurationBlock(); logger.debug(format("Channel %s Got config block getting MSP data and anchorPeers data", name)); Envelope envelope = Envelope.parseFrom(configBlock.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); // anchorPeers = Collections.unmodifiableSet(traverseConfigGroupsAnchors("", channelGroup, new HashSet<>())); } catch (TransactionException e) { logger.error(e.getMessage(), e); throw e; } catch (Exception e) { logger.error(e.getMessage(), e); throw new TransactionException(e); } } // private Set traverseConfigGroupsAnchors(String name, ConfigGroup configGroup, Set anchorPeers) throws InvalidProtocolBufferException, InvalidArgumentException { // ConfigValue anchorsConfig = configGroup.getValuesMap().get("AnchorPeers"); // if (anchorsConfig != null) { // AnchorPeers anchors = AnchorPeers.parseFrom(anchorsConfig.getValue()); // for (AnchorPeer anchorPeer : anchors.getAnchorPeersList()) { // String hostName = anchorPeer.getHost(); // int port = anchorPeer.getPort(); // logger.debug(format("parsed from config block: anchor peer %s:%d", hostName, port)); // anchorPeers.add(new Anchor(hostName, port)); // } // } // // for (Map.Entry gm : configGroup.getGroupsMap().entrySet()) { // traverseConfigGroupsAnchors(gm.getKey(), gm.getValue(), anchorPeers); // } // // return anchorPeers; // } 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()); 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; } /** * 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)); } logger.trace(format("Channel %s getConfigurationBlock returned %s", name, String.valueOf(configBlock))); 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); } } /** * 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 = getConfigurationBlock(); 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 CryptoException, TransactionException, InvalidArgumentException, 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(); ChannelHeader seekInfoHeader = createChannelHeader(HeaderType.DELIVER_SEEK_INFO, txContext.getTxID(), name, txContext.getEpoch(), getCurrentFabricTimestamp(), null); SignatureHeader signatureHeader = SignatureHeader.newBuilder() .setCreator(txContext.getIdentity().toByteString()) .setNonce(txContext.getNonce()) .build(); Header seekHeader = Header.newBuilder() .setSignatureHeader(signatureHeader.toByteString()) .setChannelHeader(seekInfoHeader.toByteString()) .build(); Payload seekPayload = Payload.newBuilder() .setHeader(seekHeader) .setData(seekInfo.toByteString()) .build(); Envelope envelope = Envelope.newBuilder().setSignature(txContext.signByteString(seekPayload.toByteArray())) .setPayload(seekPayload.toByteString()) .build(); DeliverResponse[] deliver = orderer.sendDeliver(envelope); 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(orderers); } /** * 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); } /** * 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, peers); } /** * 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.chaincodePath(instantiateProposalRequest.getChaincodePath()); instantiateProposalbuilder.chaincodeVersion(instantiateProposalRequest.getChaincodeVersion()); instantiateProposalbuilder.chaincodEndorsementPolicy(instantiateProposalRequest.getChaincodeEndorsementPolicy()); 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, peers); } /** * 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()); 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, peers); } /** * 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()); 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 { return SignedProposal.newBuilder() .setProposalBytes(proposal.toByteString()) .setSignature(transactionContext.signByteString(proposal.toByteArray())) .build(); } /** * query this channel for a Block by the block hash. * The request is sent to a random peer in the channel. * * @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 { checkChannelState(); if (blockHash == null) { throw new InvalidArgumentException("blockHash parameter is null."); } return queryBlockByHash(getRandomPeer(), blockHash); } 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 a peer in this channel for a Block by the block hash. * * @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 { checkChannelState(); checkPeer(peer); if (blockHash == null) { throw new InvalidArgumentException("blockHash parameter is null."); } ProposalResponse proposalResponse; BlockInfo responseBlock; try { logger.debug("queryBlockByHash with hash : " + Hex.encodeHexString(blockHash) + "\n to peer " + peer.getName() + " on channel " + name); QuerySCCRequest querySCCRequest = new QuerySCCRequest(client.getUserContext()); querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYHASH); querySCCRequest.setArgs(new String[] {name}); querySCCRequest.setArgBytes(new byte[][] {blockHash}); Collection proposalResponses = sendProposal(querySCCRequest, Collections.singletonList(peer)); proposalResponse = proposalResponses.iterator().next(); if (proposalResponse.getStatus().getStatus() != 200) { throw new PeerException(format("Unable to query block by hash %s %n.... for channel %s from peer %s \n with message %s", Hex.encodeHexString(blockHash), name, peer.getName(), proposalResponse.getMessage())); } responseBlock = new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload())); } catch (Exception e) { String emsg = format("queryBlockByHash hash: %s peer %s channel %s error: %s", Hex.encodeHexString(blockHash), peer.getName(), name, e.getMessage()); logger.error(emsg, e); throw new ProposalException(emsg, e); } return responseBlock; } /** * 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 * @return the {@link BlockInfo} with the given blockNumber * @throws InvalidArgumentException * @throws ProposalException */ public BlockInfo queryBlockByNumber(long blockNumber) throws InvalidArgumentException, ProposalException { return queryBlockByNumber(getRandomPeer(), blockNumber); } private Peer getRandomPeer() throws InvalidArgumentException { if (getPeers().isEmpty()) { throw new InvalidArgumentException("Channel " + name + " does not have any peers associated with it."); } return getPeers().iterator().next(); //TODO make this random } private Orderer getRandomOrderer() throws InvalidArgumentException { if (getOrderers().isEmpty()) { throw new InvalidArgumentException("Channel " + name + " does not have any orderers associated with it."); } return getOrderers().iterator().next(); //TODO make this random } 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 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 * @return the {@link BlockInfo} with the given blockNumber * @throws InvalidArgumentException * @throws ProposalException */ public BlockInfo queryBlockByNumber(Peer peer, long blockNumber) throws InvalidArgumentException, ProposalException { checkChannelState(); checkPeer(peer); ProposalResponse proposalResponse; BlockInfo responseBlock; try { logger.debug("queryBlockByNumber with blockNumber " + blockNumber + " to peer " + peer.getName() + " on channel " + name); QuerySCCRequest querySCCRequest = new QuerySCCRequest(client.getUserContext()); querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYNUMBER); querySCCRequest.setArgs(new String[] {name, Long.toUnsignedString(blockNumber)}); Collection proposalResponses = sendProposal(querySCCRequest, Collections.singletonList(peer)); proposalResponse = proposalResponses.iterator().next(); if (proposalResponse.getStatus().getStatus() != 200) { throw new PeerException(format("Unable to query block by number %d for channel %s from peer %s with message %s", blockNumber, name, peer.getName(), proposalResponse.getMessage())); } responseBlock = new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload())); } catch (Exception e) { String emsg = format("queryBlockByNumber blockNumber %d peer %s channel %s error %s", blockNumber, peer.getName(), name, e.getMessage()); logger.error(emsg, e); throw new ProposalException(emsg, e); } return responseBlock; } /** * 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 * @return the {@link BlockInfo} for the Block containing the transaction * @throws InvalidArgumentException * @throws ProposalException */ public BlockInfo queryBlockByTransactionID(String txID) throws InvalidArgumentException, ProposalException { return queryBlockByTransactionID(getRandomPeer(), 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 * @return the {@link BlockInfo} for the Block containing the transaction * @throws InvalidArgumentException * @throws ProposalException */ public BlockInfo queryBlockByTransactionID(Peer peer, String txID) throws InvalidArgumentException, ProposalException { checkChannelState(); checkPeer(peer); if (txID == null) { throw new InvalidArgumentException("TxID parameter is null."); } ProposalResponse proposalResponse; BlockInfo responseBlock; try { logger.debug("queryBlockByTransactionID with txID " + txID + " \n to peer" + peer.getName() + " on channel " + name); QuerySCCRequest querySCCRequest = new QuerySCCRequest(client.getUserContext()); querySCCRequest.setFcn(QuerySCCRequest.GETBLOCKBYTXID); querySCCRequest.setArgs(new String[] {name, txID}); Collection proposalResponses = sendProposal(querySCCRequest, Collections.singletonList(peer)); proposalResponse = proposalResponses.iterator().next(); if (proposalResponse.getStatus().getStatus() != 200) { throw new PeerException(format("Unable to query block by TxID %s%n for channel %s from peer %s with message %s", txID, name, peer.getName(), proposalResponse.getMessage())); } responseBlock = new BlockInfo(Block.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload())); } catch (Exception e) { String emsg = format("QueryBlockByTransactionID TxID %s%n peer %s channel %s error %s", txID, peer.getName(), name, e.getMessage()); logger.error(emsg, e); throw new ProposalException(emsg, e); } return responseBlock; } /** * query this channel for chain information. * The request is sent to a random peer in the channel * * @return a {@link BlockchainInfo} object containing the chain info requested * @throws InvalidArgumentException * @throws ProposalException */ public BlockchainInfo queryBlockchainInfo() throws ProposalException, InvalidArgumentException { return queryBlockchainInfo(getRandomPeer()); } /** * query for chain information * * @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 { checkChannelState(); checkPeer(peer); BlockchainInfo response; try { logger.debug("queryBlockchainInfo to peer " + peer.getName() + " on channel " + name); QuerySCCRequest querySCCRequest = new QuerySCCRequest(client.getUserContext()); querySCCRequest.setFcn(QuerySCCRequest.GETCHAININFO); querySCCRequest.setArgs(new String[] {name}); Collection proposalResponses = sendProposal(querySCCRequest, Collections.singletonList(peer)); ProposalResponse proposalResponse = proposalResponses.iterator().next(); if (proposalResponse.getStatus().getStatus() != 200) { throw new PeerException(format("Unable to query block channel info for channel %s from peer %s with message %s", name, peer.getName(), proposalResponse.getMessage())); } response = new BlockchainInfo(Ledger.BlockchainInfo.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload())); } catch (Exception e) { String emsg = format("queryBlockchainInfo peer %s channel %s error %s", peer.getName(), name, e.getMessage()); logger.error(emsg, e); throw new ProposalException(emsg, e); } return response; } /** * Query this channel for a Fabric Transaction given its transactionID. * The request is sent to a random peer in the channel. * * @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(getRandomPeer(), txID); } /** * Query for a Fabric Transaction given its transactionID * * @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 { checkChannelState(); checkPeer(peer); if (txID == null) { throw new InvalidArgumentException("TxID parameter is null."); } TransactionInfo transactionInfo; try { logger.debug("queryTransactionByID with txID " + txID + "\n from peer " + peer.getName() + " on channel " + name); QuerySCCRequest querySCCRequest = new QuerySCCRequest(client.getUserContext()); querySCCRequest.setFcn(QuerySCCRequest.GETTRANSACTIONBYID); querySCCRequest.setArgs(new String[] {name, txID}); Collection proposalResponses = sendProposal(querySCCRequest, Collections.singletonList(peer)); ProposalResponse proposalResponse = proposalResponses.iterator().next(); if (proposalResponse.getStatus().getStatus() != 200) { throw new PeerException(format("Unable to query transaction info for ID %s%n for channel %s from peer %s with message %s", txID, name, peer.getName(), proposalResponse.getMessage())); } transactionInfo = new TransactionInfo(txID, ProcessedTransaction.parseFrom(proposalResponse.getProposalResponse().getResponse().getPayload())); } catch (Exception e) { String emsg = format("queryTransactionByID TxID %s%n peer %s channel %s error %s", txID, peer.getName(), name, e.getMessage()); logger.error(emsg, e); throw new ProposalException(emsg, e); } return transactionInfo; } 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 * * @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 { checkChannelState(); checkPeer(peer); try { TransactionContext context = getTransactionContext(); 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); } } /** * Send a transaction proposal. * * @param transactionProposalRequest The transaction proposal to be sent to all the peers. * @return responses from peers. * @throws InvalidArgumentException * @throws ProposalException */ public Collection sendTransactionProposal(TransactionProposalRequest transactionProposalRequest) throws ProposalException, InvalidArgumentException { return sendProposal(transactionProposalRequest, peers); } /** * 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 sendProposal(queryByChaincodeRequest, peers); } /** * 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); } private Collection sendProposal(TransactionRequest proposalRequest, Collection peers) throws InvalidArgumentException, ProposalException { checkChannelState(); checkPeers(peers); if (null == proposalRequest) { throw new InvalidArgumentException("sendProposal queryProposalRequest 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 Collection sendProposalToPeers(Collection peers, SignedProposal signedProposal, TransactionContext transactionContext) throws InvalidArgumentException, ProposalException { checkPeers(peers); 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 peer %s at url %s", name, peer.getName(), peer.getUrl())); if (null != diagnosticFileDumper) { logger.trace(format("Sending to channel %s, peer: %s, proposal: %s", name, peer.getName(), 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.getName(); try { fabricResponse = peerFuturePair.future.get(transactionContext.getProposalWaitTime(), TimeUnit.MILLISECONDS); message = fabricResponse.getResponse().getMessage(); status = fabricResponse.getResponse().getStatus(); logger.debug(format("Channel %s got back from peer %s status: %d, message: %s", name, 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 to " + peerName + " failed because of interruption"; logger.error(message, e); } catch (TimeoutException e) { message = format("Sending proposal to " + peerName + " failed because of timeout(%d milliseconds) expiration", transactionContext.getProposalWaitTime()); logger.error(message, e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Error) { String emsg = "Sending proposal 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("Sending proposal to " + peerName + " failed because of: gRPC failure=%s", ((StatusRuntimeException) cause).getStatus()); } else { message = format("Sending proposal to " + peerName + " failed because of: %s", cause.getMessage()); } logger.error(message, new Exception(cause)); //wrapped in exception to get full stack trace. } } ProposalResponse proposalResponse = new ProposalResponse(transactionContext.getTxID(), transactionContext.getChannelID(), 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; } ///////////////////////////////////////////////////////// // transactions order /** * 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, orderers, 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, orderers); } /** * 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()); } /** * Send transaction to one of a specified set of orderers with the specified user context. * * @param proposalResponses * @param orderers * @return Future allowing access to the result of the transaction invocation. */ public CompletableFuture sendTransaction(Collection proposalResponses, Collection orderers, User userContext) { try { checkChannelState(); userContextCheck(userContext); if (null == proposalResponses) { throw new InvalidArgumentException("sendTransaction proposalResponses was null"); } if (null == orderers) { throw new InvalidArgumentException("sendTransaction Orderers is null"); } if (orderers.isEmpty()) { throw new InvalidArgumentException("sendTransaction Orderers to send to is empty."); } 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; for (ProposalResponse sdkProposalResponse : proposalResponses) { ed.add(sdkProposalResponse.getProposalResponse().getEndorsement()); if (proposal == null) { proposal = sdkProposalResponse.getProposal(); proposalTransactionID = sdkProposalResponse.getTransactionID(); proposalResponsePayload = sdkProposalResponse.getProposalResponse().getPayload(); } } TransactionBuilder transactionBuilder = TransactionBuilder.newBuilder(); Payload transactionPayload = transactionBuilder .chaincodeProposal(proposal) .endorsements(ed) .proposalResponsePayload(proposalResponsePayload).build(); Envelope transactionEnvelope = createTransactionEnvelope(transactionPayload, userContext); CompletableFuture sret = registerTxListener(proposalTransactionID); logger.debug(format("Channel %s sending transaction to orderer(s) with TxID %s ", name, proposalTransactionID)); boolean success = false; BroadcastResponse resp = null; for (Orderer orderer : orderers) { 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); if (resp.getStatus() == Status.SUCCESS) { success = true; break; } } catch (Exception e) { String emsg = format("Channel %s unsuccessful sendTransaction to orderer", name); if (resp != null) { StringBuilder respdata = new StringBuilder(400); Status status = resp.getStatus(); if (null != status) { respdata.append(status.name()); respdata.append("-"); respdata.append(status.getNumber()); } emsg = format("Channel %s unsuccessful sendTransaction to orderer. %s", name, respdata.toString()); } logger.error(emsg, e); } } if (success) { logger.debug(format("Channel %s successful sent to Orderer transaction id: %s", name, proposalTransactionID)); return sret; } else { 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 emsg = format("Channel %s failed to place transaction %s on Orderer. Cause: UNSUCCESSFUL. %s", name, proposalTransactionID, respdata.toString()); CompletableFuture ret = new CompletableFuture<>(); ret.completeExceptionally(new Exception(emsg)); return ret; } } catch (Exception e) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(e); return future; } } private Envelope createTransactionEnvelope(Payload transactionPayload, User user) throws CryptoException { return Envelope.newBuilder() .setPayload(transactionPayload.toByteString()) .setSignature(ByteString.copyFrom(client.getCryptoSuite().sign(user.getEnrollment().getKey(), 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"); } } //////////////// Channel Block monitoring ////////////////////////////////// /** * Register a block listener. * * @param listener * @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(); } 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."); } } /** * 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); } } /** * A queue each eventing hub will write events to. */ private final ChannelEventQue channelEventQue = new ChannelEventQue(); 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.getEvent().getEventCase() != EventCase.BLOCK) { 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(eventException); } else { logger.warn(e); if (eventException != null) { EventHubException eve = new EventHubException(eventException); 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; } } /** * Runs processing events from event hubs. */ Thread eventQueueThread = null; private void startEventQue() { if (eventQueueThread != null) { return; } executorService.execute(() -> { eventQueueThread = Thread.currentThread(); while (!shutdown) { final BlockEvent blockEvent; try { blockEvent = channelEventQue.getNextEvent(); } catch (EventHubException e) { if (!shutdown) { logger.error(e); } continue; } if (blockEvent == null) { continue; } try { final String blockchainID = blockEvent.getChannelId(); if (!Objects.equals(name, blockchainID)) { 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 { executorService.execute(() -> l.listener.received(blockEvent)); } catch (Throwable e) { //Don't let one register stop rest. logger.error("Error trying to call block listener on channel " + blockEvent.getChannelId(), e); } } } catch (Exception e) { logger.error("Unable to parse event", e); logger.debug("event:\n)"); logger.debug(blockEvent.toString()); } } }); } private static final String BLOCK_LISTENER_TAG = "BLOCK_LISTENER_HANDLE"; private final LinkedHashMap blockListeners = new LinkedHashMap<>(); class BL { final BlockListener listener; public String getHandle() { return handle; } 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); } } } ////////// Transaction monitoring ///////////////////////////// /** * 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 -> { if (txListeners.isEmpty()) { return; } for (TransactionEvent transactionEvent : blockEvent.getTransactionEvents()) { logger.debug(format("Channel %s got event for transaction %s ", name, transactionEvent.getTransactionID())); 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 (getEventHubs().size() == l.eventReceived(transactionEvent.getEventHub()).size()) { l.fire(transactionEvent); } } catch (Throwable e) { logger.error(e); // Don't let one register stop rest. } } } }); } private final LinkedHashMap> txListeners = new LinkedHashMap<>(); private class TL { final String txID; final AtomicBoolean fired = new AtomicBoolean(false); final CompletableFuture future; final Set seenEventHubs = Collections.synchronizedSet(new HashSet<>()); Set eventReceived(EventHub eventHub) { logger.debug(format("Channel %s seen transaction event %s for eventHub %s", name, txID, eventHub.toString())); seenEventHubs.add(eventHub); return seenEventHubs; } TL(String txID, CompletableFuture future) { this.txID = txID; this.future = future; addListener(); } private void addListener() { synchronized (txListeners) { LinkedList tl = txListeners.computeIfAbsent(txID, k -> new LinkedList<>()); tl.add(this); } } 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()) { executorService.execute(() -> future.complete(transactionEvent)); } else { executorService.execute(() -> future.completeExceptionally( new TransactionEventException(format("Received invalid transaction event. Transaction ID %s status %s", transactionEvent.getTransactionID(), transactionEvent.getValidationCode()), transactionEvent))); } } } /** * Register a transactionId that to get notification on when the event is seen in the block chain. * * @param txid * @return */ private CompletableFuture registerTxListener(String txid) { CompletableFuture future = new CompletableFuture<>(); new TL(txid, future); return future; } //////////////////////////////////////////////////////////////////////// //////////////// Chaincode Events.. ////////////////////////////////// private static final String CHAINCODE_EVENTS_TAG = "CHAINCODE_EVENTS_HANDLE"; private final LinkedHashMap chainCodeListeners = new LinkedHashMap<>(); 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) { executorService.execute(() -> chaincodeEventListener.received(handle, blockEvent, ce)); } } /** * 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; } private String blh = null; /** * 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; } 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()) { HashMap matches = new HashMap<>(); //Find matches. synchronized (chainCodeListeners) { for (ChaincodeEventListenerEntry chaincodeEventListenerEntry : chainCodeListeners.values()) { for (ChaincodeEvent chaincodeEvent : chaincodeEvents) { if (chaincodeEventListenerEntry.isMatch(chaincodeEvent)) { matches.put(chaincodeEventListenerEntry, chaincodeEvent); } } } } //fire events for (Map.Entry match : matches.entrySet()) { ChaincodeEventListenerEntry chaincodeEventListenerEntry = match.getKey(); ChaincodeEvent ce = match.getValue(); 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; } initialized = false; shutdown = true; executorService = null; chainCodeListeners.clear(); blockListeners.clear(); for (EventHub eh : getEventHubs()) { try { eh.shutdown(); } catch (Exception e) { // Best effort. } } eventHubs.clear(); for (Peer peer : getPeers()) { try { peer.shutdown(force); } catch (Exception e) { // Best effort. } } peers.clear(); for (Orderer orderer : getOrderers()) { orderer.shutdown(force); } orderers.clear(); if (eventQueueThread != null) { eventQueueThread.interrupt(); } eventQueueThread = null; } @Override protected void finalize() throws Throwable { shutdown(true); super.finalize(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy