Skip to content

Commit

Permalink
FAB-8392 Include META-INF in chaincode packaging.
Browse files Browse the repository at this point in the history
Change-Id: I7730e10205c4a09805eb03d2b4d1d329c5647da2
Signed-off-by: rickr <cr22rc@gmail.com>
  • Loading branch information
cr22rc committed Feb 27, 2018
1 parent cd93543 commit c1babcf
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/hyperledger/fabric/sdk/Channel.java
Original file line number Diff line number Diff line change
Expand Up @@ -1357,7 +1357,7 @@ private int seekBlock(SeekInfo seekInfo, List<DeliverResponse> deliverResponses,

TransactionContext txContext = getTransactionContext();

DeliverResponse[] deliver = orderer.sendDeliver(createSeekInfoEnvelope(txContext, seekInfo, null));
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));
Expand Down Expand Up @@ -1565,6 +1565,7 @@ Collection<ProposalResponse> sendInstallProposal(InstallProposalRequest installP
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,34 @@ public class InstallProposalRequest extends TransactionRequest {

private File chaincodeSourceLocation = null;
private InputStream chaincodeInputStream = null;
private File chaincodeMetaInfLocation = null;

File getChaincodeMetaInfLocation() {
return chaincodeMetaInfLocation;
}

/**
* Set the META-INF directory to be used for packaging chaincode.
* Only applies if source location {@link #chaincodeSourceLocation} for the chaincode is set.
*
* @param chaincodeMetaInfLocation The directory where the "META-INF" directory is located..
* @see <a href="http://hyperledger-fabric.readthedocs.io/en/master/couchdb_as_state_database.html#using-couchdb-from-chaincode">
* Fabric Read the docs couchdb as a state database
* </a>
*/

public void setChaincodeMetaInfLocation(File chaincodeMetaInfLocation) throws InvalidArgumentException {
if (chaincodeMetaInfLocation == null) {
throw new InvalidArgumentException("Chaincode META-INF location may not be null.");
}

if (chaincodeInputStream != null) {
throw new InvalidArgumentException("Chaincode META-INF location may not be set with chaincode input stream set.");
}
this.chaincodeMetaInfLocation = chaincodeMetaInfLocation;
}



InstallProposalRequest(User userContext) {
super(userContext);
Expand All @@ -37,6 +65,8 @@ public InputStream getChaincodeInputStream() {

/**
* Chaincode input stream containing the actual chaincode. Only format supported is a tar zip compressed input of the source.
* Only input stream or source location maybe used at the same time.
* The contents of the stream are not validated or inspected by the SDK.
*
* @param chaincodeInputStream
* @throws InvalidArgumentException
Expand All @@ -49,6 +79,9 @@ public void setChaincodeInputStream(InputStream chaincodeInputStream) throws Inv
if (chaincodeSourceLocation != null) {
throw new InvalidArgumentException("Error setting chaincode input stream. Chaincode source location already set. Only one or the other maybe set.");
}
if (chaincodeMetaInfLocation != null) {
throw new InvalidArgumentException("Error setting chaincode input stream. Chaincode META-INF location already set. Only one or the other maybe set.");
}
this.chaincodeInputStream = chaincodeInputStream;
}

Expand All @@ -58,6 +91,7 @@ public File getChaincodeSourceLocation() {

/**
* The location of the chaincode.
* Chaincode input stream and source location can not both be set.
*
* @param chaincodeSourceLocation
* @throws InvalidArgumentException
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/hyperledger/fabric/sdk/Orderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class Orderer implements Serializable {
private transient boolean shutdown = false;
private Channel channel;
private transient volatile OrdererClient ordererClient = null;
private transient byte[] clientTLSCertificateDigest;

Orderer(String name, String url, Properties properties) throws InvalidArgumentException {

Expand All @@ -63,6 +64,13 @@ static Orderer createNewInstance(String name, String url, Properties properties)

}

byte[] getClientTLSCertificateDigest() {
if (null == clientTLSCertificateDigest) {
clientTLSCertificateDigest = new Endpoint(url, properties).getClientTLSCertificateDigest();
}
return clientTLSCertificateDigest;
}

/**
* Get Orderer properties.
*
Expand Down
59 changes: 50 additions & 9 deletions src/main/java/org/hyperledger/fabric/sdk/helper/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -49,13 +50,15 @@
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.codec.binary.Hex.encodeHexString;

public final class Utils {
private static final Log logger = LogFactory.getLog(Utils.class);

private static final boolean TRACE_ENABED = logger.isTraceEnabled();
private static final Config config = Config.getConfig();
private static final int MAX_LOG_STRING_LENGTH = config.maxLogStringLength();

Expand All @@ -68,7 +71,7 @@ public final class Utils {
* @return hash of path, func and args
*/
public static String generateParameterHash(String path, String func, List<String> args) {
logger.debug(String.format("GenerateParameterHash : path=%s, func=%s, args=%s", path, func, args));
logger.debug(format("GenerateParameterHash : path=%s, func=%s, args=%s", path, func, args));

// Append the arguments
StringBuilder param = new StringBuilder(path);
Expand Down Expand Up @@ -100,7 +103,7 @@ public static String generateDirectoryHash(String rootDir, String chaincodeDir,

File dir = projectPath.toFile();
if (!dir.exists() || !dir.isDirectory()) {
throw new IOException(String.format("The chaincode path \"%s\" is invalid", projectPath));
throw new IOException(format("The chaincode path \"%s\" is invalid", projectPath));
}

StringBuilder hashBuilder = new StringBuilder(hash);
Expand All @@ -115,26 +118,31 @@ public static String generateDirectoryHash(String rootDir, String chaincodeDir,
hashBuilder.setLength(0);
hashBuilder.append(Hex.toHexString(hash(toHash, new SHA3Digest())));
} catch (IOException ex) {
throw new RuntimeException(String.format("Error while reading file %s", file.getAbsolutePath()), ex);
throw new RuntimeException(format("Error while reading file %s", file.getAbsolutePath()), ex);
}
});

// If original hash and final hash are the same, it indicates that no new contents were found
if (hashBuilder.toString().equals(hash)) {
throw new IOException(String.format("The chaincode directory \"%s\" has no files", projectPath));
throw new IOException(format("The chaincode directory \"%s\" has no files", projectPath));
}
return hashBuilder.toString();
}

/**
* Compress the contents of given directory using Tar and Gzip to an in-memory byte array.
*
* @param sourceDirectory the source directory.
* @param pathPrefix a path to be prepended to every file name in the .tar.gz output, or {@code null} if no prefix is required.
* @param sourceDirectory the source directory.
* @param pathPrefix a path to be prepended to every file name in the .tar.gz output, or {@code null} if no prefix is required.
* @param chaincodeMetaInf
* @return the compressed directory contents.
* @throws IOException
*/
public static byte[] generateTarGz(File sourceDirectory, String pathPrefix) throws IOException {
public static byte[] generateTarGz(File sourceDirectory, String pathPrefix, File chaincodeMetaInf) throws IOException {
logger.trace(format("generateTarGz: sourceDirectory: %s, pathPrefix: %s, chaincodeMetaInf: %s",
sourceDirectory == null ? "null" : sourceDirectory.getAbsolutePath(), pathPrefix,
chaincodeMetaInf == null ? "null" : chaincodeMetaInf.getAbsolutePath()));

ByteArrayOutputStream bos = new ByteArrayOutputStream(500000);

String sourcePath = sourceDirectory.getAbsolutePath();
Expand All @@ -157,6 +165,10 @@ public static byte[] generateTarGz(File sourceDirectory, String pathPrefix) thro

relativePath = FilenameUtils.separatorsToUnix(relativePath);

if (TRACE_ENABED) {
logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath()));
}

archiveEntry = new TarArchiveEntry(childFile, relativePath);
fileInputStream = new FileInputStream(childFile);
archiveOutputStream.putArchiveEntry(archiveEntry);
Expand All @@ -167,6 +179,35 @@ public static byte[] generateTarGz(File sourceDirectory, String pathPrefix) thro
IOUtils.closeQuietly(fileInputStream);
archiveOutputStream.closeArchiveEntry();
}

}

if (null != chaincodeMetaInf) {
childrenFiles = org.apache.commons.io.FileUtils.listFiles(chaincodeMetaInf, null, true);

final URI metabase = chaincodeMetaInf.toURI();

for (File childFile : childrenFiles) {

final String relativePath = Paths.get("META-INF", metabase.relativize(childFile.toURI()).getPath()).toString();

if (TRACE_ENABED) {
logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath()));
}

archiveEntry = new TarArchiveEntry(childFile, relativePath);
fileInputStream = new FileInputStream(childFile);
archiveOutputStream.putArchiveEntry(archiveEntry);

try {
IOUtils.copy(fileInputStream, archiveOutputStream);
} finally {
IOUtils.closeQuietly(fileInputStream);
archiveOutputStream.closeArchiveEntry();
}

}

}
} finally {
IOUtils.closeQuietly(archiveOutputStream);
Expand Down Expand Up @@ -286,7 +327,7 @@ public static Properties parseGrpcUrl(String url) {

String protocol = props.getProperty("protocol");
if (!"grpc".equals(protocol) && !"grpcs".equals(protocol)) {
throw new RuntimeException(String.format("Invalid protocol expected grpc or grpcs and found %s.", protocol));
throw new RuntimeException(format("Invalid protocol expected grpc or grpcs and found %s.", protocol));
}
} else {
throw new RuntimeException("URL must be of the format protocol://host:port");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class InstallProposalBuilder extends LSCCProposalBuilder {
private TransactionRequest.Type chaincodeLanguage;
protected String action = "install";
private InputStream chaincodeInputStream;
private File chaincodeMetaInfLocation;

protected InstallProposalBuilder() {
super();
Expand Down Expand Up @@ -88,6 +89,12 @@ public InstallProposalBuilder setChaincodeSource(File chaincodeSource) {
return this;
}

public InstallProposalBuilder setChaincodeMetaInfLocation(File chaincodeMetaInfLocation) {

this.chaincodeMetaInfLocation = chaincodeMetaInfLocation;
return this;
}

@Override
public FabricProposal.Proposal build() throws ProposalException, InvalidArgumentException {

Expand Down Expand Up @@ -123,6 +130,40 @@ private void createNetModeTransaction() throws IOException {
String targetPathPrefix = null;
String dplang;

File metainf = null;
if (null != chaincodeMetaInfLocation) {
if (!chaincodeMetaInfLocation.exists()) {
throw new IllegalArgumentException(format("Directory to find chaincode META-INF %s does not exist", chaincodeMetaInfLocation.getAbsolutePath()));
}

if (!chaincodeMetaInfLocation.isDirectory()) {
throw new IllegalArgumentException(format("Directory to find chaincode META-INF %s is not a directory", chaincodeMetaInfLocation.getAbsolutePath()));
}
metainf = new File(chaincodeMetaInfLocation, "META-INF");
logger.trace("META-INF directory is " + metainf.getAbsolutePath());
if (!metainf.exists()) {

throw new IllegalArgumentException(format("The META-INF directory does not exist in %s", chaincodeMetaInfLocation.getAbsolutePath()));
}

if (!metainf.isDirectory()) {
throw new IllegalArgumentException(format("The META-INF in %s is not a directory.", chaincodeMetaInfLocation.getAbsolutePath()));
}
File[] files = metainf.listFiles();

if (files == null) {
throw new IllegalArgumentException("null for listFiles on: " + chaincodeMetaInfLocation.getAbsolutePath());
}

if (files.length < 1) {

throw new IllegalArgumentException(format("The META-INF directory %s is empty.", metainf.getAbsolutePath()));
}

logger.trace(format("chaincode META-INF found %s", metainf.getAbsolutePath()));

}

switch (chaincodeLanguage) {
case GO_LANG:

Expand Down Expand Up @@ -204,7 +245,7 @@ private void createNetModeTransaction() throws IOException {
chaincodeID, dplang, projectSourceDir.getAbsolutePath(), targetPathPrefix, chaincodePath));

// generate chaincode source tar
data = Utils.generateTarGz(projectSourceDir, targetPathPrefix);
data = Utils.generateTarGz(projectSourceDir, targetPathPrefix, metainf);

if (null != diagnosticFileDumper) {

Expand All @@ -219,8 +260,8 @@ private void createNetModeTransaction() throws IOException {
data = IOUtils.toByteArray(chaincodeInputStream);

if (null != diagnosticFileDumper) {
logger.trace(format("Installing '%s' language %s chaincode from input stream",
chaincodeID, dplang));
logger.trace(format("Installing '%s' language %s chaincode from input stream tar file dump %s",
chaincodeID, dplang, diagnosticFileDumper.createDiagnosticTarFile(data)));
}

}
Expand Down
4 changes: 4 additions & 0 deletions src/test/fixture/meta-infs/emptyMetaInf/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"who?": "rick did this!"
}
Loading

0 comments on commit c1babcf

Please sign in to comment.