Signed-off-by:Renjith K N <renjithkn@gmail.com>

Signed-off-by: FIRST_NAME LAST_NAME <renjithkn@gamil.com>
Signed-off-by: renjithpta <renjithkn@gmail.com>
This commit is contained in:
FIRST_NAME LAST_NAME 2022-04-01 07:29:02 +00:00 committed by renjithpta
parent 62b4131cf5
commit c31137ecab
185 changed files with 21265 additions and 6485 deletions

View file

@ -54,6 +54,8 @@ Additional samples demonstrate various Fabric use cases and application patterns
| [Off chain data](off_chain_data) | Learn how to use the Peer channel-based event services to build an off-chain database for reporting and analytics. | [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) | | [Off chain data](off_chain_data) | Learn how to use the Peer channel-based event services to build an off-chain database for reporting and analytics. | [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) |
| [Token ERC-20](token-erc-20) | Smart contract demonstrating how to create and transfer fungible tokens using an account-based model. | [README](token-erc-20/README.md) | | [Token ERC-20](token-erc-20) | Smart contract demonstrating how to create and transfer fungible tokens using an account-based model. | [README](token-erc-20/README.md) |
| [Token UTXO](token-utxo) | Smart contract demonstrating how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. | [README](token-utxo/README.md) | | [Token UTXO](token-utxo) | Smart contract demonstrating how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. | [README](token-utxo/README.md) |
| [Token ERC-1155](token-erc-1155) | Smart contract demonstrating how to create and transfer multiple tokens (both fungible and non-fungible) using an account based model. | [README](token-erc-1155/README.md) |
| [Token ERC-721](token-erc-721) | Smart contract demonstrating how to create and transfer non-fungible tokens using an account-based model. | [README](token-erc-721/README.md) |
| [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | [README](high-throughput/README.md) | | [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | [README](high-throughput/README.md) |
| [Simple Auction](auction-simple) | Run an auction where bids are kept private until the auction is closed, after which users can reveal their bid. | [README](auction-simple/README.md) | | [Simple Auction](auction-simple) | Run an auction where bids are kept private until the auction is closed, after which users can reveal their bid. | [README](auction-simple/README.md) |
| [Dutch Auction](auction-dutch) | Run an auction in which multiple items of the same type can be sold to more than one buyer. This example also includes the ability to add an auditor organization. | [README](auction-dutch/README.md) | | [Dutch Auction](auction-dutch) | Run an auction in which multiple items of the same type can be sold to more than one buyer. This example also includes the ability to add an auditor organization. | [README](auction-dutch/README.md) |

View file

@ -0,0 +1,78 @@
# Asset transfer basic sample
The asset transfer basic sample demonstrates:
- Connecting a client application to a Fabric blockchain network.
- Submitting smart contract transactions to update ledger state.
- Evaluating smart contract transactions to query ledger state.
- Handling errors in transaction invocation.
## About the sample
This sample includes smart contract and application code in multiple languages. This sample shows create, read, update, transfer and delete of an asset.
For a more detailed walk-through of the application code and client API usage, refer to the [Running a Fabric Application tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) in the main Hyperledger Fabric documentation.
### Application
Follow the execution flow in the client application code, and corresponding output on running the application. Pay attention to the sequence of:
- Transaction invocations (console output like "**--> Submit Transaction**" and "**--> Evaluate Transaction**").
- Results returned by transactions (console output like "**\*\*\* Result**").
### Smart Contract
The smart contract (in folder `chaincode-xyz`) implements the following functions to support the application:
- CreateAsset
- ReadAsset
- UpdateAsset
- DeleteAsset
- TransferAsset
Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to demonstrate how to invoke transactions.
## Running the sample
The Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
1. Deploy one of the smart contract implementations (from the `test-network` folder).
```
# To deploy the TypeScript chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
# To deploy the Go chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
# To deploy the Java chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
```
1. Run the application (from the `asset-transfer-basic` folder).
```
# To run the Typescript sample application
cd application-gateway-typescript
npm install
npm start
# To run the Go sample application
cd application-gateway-go
go run .
# To run the Java sample application
cd application-gateway-java
./gradlew run
```
## Clean up
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down
```

View file

@ -0,0 +1,32 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java project to get you started.
* For more details take a look at the Java Quickstart chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.5/userguide/tutorial_java_projects.html
*/
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
ext {
javaMainClass = "application.java.App"
}
repositories {
mavenCentral()
}
dependencies {
// This dependency is used by the application.
implementation 'org.hyperledger.fabric:fabric-gateway:1.0.0'
implementation 'io.grpc:grpc-netty-shaded:1.42.0'
implementation 'com.google.code.gson:gson:2.8.9'
}
application {
// Define the main class for the application.
mainClass = 'App'
}

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
*/
rootProject.name = 'application-java'

View file

@ -0,0 +1,255 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import org.hyperledger.fabric.client.CallOption;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.GatewayException;
import org.hyperledger.fabric.client.Network;
import org.hyperledger.fabric.client.Status;
import org.hyperledger.fabric.client.SubmitException;
import org.hyperledger.fabric.client.SubmittedTransaction;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
import org.hyperledger.fabric.client.identity.Signers;
import org.hyperledger.fabric.client.identity.X509Identity;
import org.hyperledger.fabric.protos.gateway.ErrorDetail;
public final class App {
private static final String mspID = "Org1MSP";
private static final String channelName = "mychannel";
private static final String chaincodeName = "basic";
// Path to crypto materials.
private static final Path cryptoPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com");
// Path to user certificate.
private static final Path certPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "signcerts", "cert.pem"));
// Path to user private key directory.
private static final Path keyDirPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "keystore"));
// Path to peer tls certificate.
private static final Path tlsCertPath = cryptoPath.resolve(Paths.get("peers", "peer0.org1.example.com", "tls", "ca.crt"));
// Gateway peer end point.
private static final String peerEndpoint = "localhost:7051";
private static final String overrideAuth = "peer0.org1.example.com";
private final Contract contract;
private final String assetId = "asset" + Instant.now().toEpochMilli();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void main(final String[] args) throws Exception {
// The gRPC client connection should be shared by all Gateway connections to
// this endpoint.
ManagedChannel channel = newGrpcConnection();
Gateway.Builder builder = Gateway.newInstance().identity(newIdentity()).signer(newSigner()).connection(channel)
// Default timeouts for different gRPC calls
.evaluateOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(CallOption.deadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(CallOption.deadlineAfter(1, TimeUnit.MINUTES));
try (Gateway gateway = builder.connect()) {
new App(gateway).run();
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
private static ManagedChannel newGrpcConnection() throws IOException, CertificateException {
Reader tlsCertReader = Files.newBufferedReader(tlsCertPath);
X509Certificate tlsCert = Identities.readX509Certificate(tlsCertReader);
return NettyChannelBuilder.forTarget(peerEndpoint)
.sslContext(GrpcSslContexts.forClient().trustManager(tlsCert).build()).overrideAuthority(overrideAuth)
.build();
}
private static Identity newIdentity() throws IOException, CertificateException {
Reader certReader = Files.newBufferedReader(certPath);
X509Certificate certificate = Identities.readX509Certificate(certReader);
return new X509Identity(mspID, certificate);
}
private static Signer newSigner() throws IOException, InvalidKeyException {
Path keyPath = Files.list(keyDirPath)
.findFirst()
.orElseThrow();
Reader keyReader = Files.newBufferedReader(keyPath);
PrivateKey privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
public App(final Gateway gateway) {
// Get a network instance representing the channel where the smart contract is
// deployed.
Network network = gateway.getNetwork(channelName);
// Get the smart contract from the network.
contract = network.getContract(chaincodeName);
}
public void run() throws GatewayException, CommitException {
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
initLedger();
// Return all the current assets on the ledger.
getAllAssets();
// Create a new asset on the ledger.
createAsset();
// Update an existing asset asynchronously.
transferAssetAsync();
// Get the asset details by assetID.
readAssetById();
// Update an asset which does not exist.
updateNonExistentAsset();
}
/**
* This type of transaction would typically only be run once by an application
* the first time it was started after its initial deployment. A new version of
* the chaincode deployed later would likely not need to run an "init" function.
*/
private void initLedger() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger");
contract.submitTransaction("InitLedger");
System.out.println("*** Transaction committed successfully");
}
/**
* Evaluate a transaction to query ledger state.
*/
private void getAllAssets() throws GatewayException {
System.out.println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger");
byte[] result = contract.evaluateTransaction("GetAllAssets");
System.out.println("*** Result: " + prettyJson(result));
}
private String prettyJson(final byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private String prettyJson(final String json) {
JsonElement parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
/**
* Submit a transaction synchronously, blocking until it has been committed to
* the ledger.
*/
private void createAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments");
contract.submitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300");
System.out.println("*** Transaction committed successfully");
}
/**
* Submit transaction asynchronously, allowing the application to process the
* smart contract response (e.g. update a UI) while waiting for the commit
* notification.
*/
private void transferAssetAsync() throws EndorseException, SubmitException, CommitStatusException {
System.out.println("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner");
SubmittedTransaction commit = contract.newProposal("TransferAsset")
.addArguments(assetId, "Saptha")
.build()
.endorse()
.submitAsync();
byte[] result = commit.getResult();
String oldOwner = new String(result, StandardCharsets.UTF_8);
System.out.println("*** Successfully submitted transaction to transfer ownership from " + oldOwner + " to Saptha");
System.out.println("*** Waiting for transaction commit");
Status status = commit.getStatus();
if (!status.isSuccessful()) {
throw new RuntimeException("Transaction " + status.getTransactionId() +
" failed to commit with status code " + status.getCode());
}
System.out.println("*** Transaction committed successfully");
}
private void readAssetById() throws GatewayException {
System.out.println("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes");
byte[] evaluateResult = contract.evaluateTransaction("ReadAsset", assetId);
System.out.println("*** Result:" + prettyJson(evaluateResult));
}
/**
* submitTransaction() will throw an error containing details of any error
* responses from the smart contract.
*/
private void updateNonExistentAsset() {
try {
System.out.println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error");
contract.submitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300");
System.out.println("******** FAILED to return an error");
} catch (EndorseException | SubmitException | CommitStatusException e) {
System.out.println("*** Successfully caught the error: ");
e.printStackTrace(System.out);
System.out.println("Transaction ID: " + e.getTransactionId());
List<ErrorDetail> details = e.getDetails();
if (!details.isEmpty()) {
System.out.println("Error Details:");
for (ErrorDetail detail : details) {
System.out.println("- address: " + detail.getAddress() + ", mspId: " + detail.getMspId()
+ ", message: " + detail.getMessage());
}
}
} catch (CommitException e) {
System.out.println("*** Successfully caught the error: " + e);
e.printStackTrace(System.out);
System.out.println("Transaction ID: " + e.getTransactionId());
System.out.println("Status code: " + e.getCode());
}
}
}

View file

@ -11,29 +11,35 @@ import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { TextDecoder } from 'util'; import { TextDecoder } from 'util';
const channelName = 'mychannel'; const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
const chaincodeName = 'basic'; const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
const mspId = 'Org1MSP'; const mspId = envOrDefault('MSP_ID', 'Org1MSP');
// Path to crypto materials. // Path to crypto materials.
const cryptoPath = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'); const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'));
// Path to user private key directory. // Path to user private key directory.
const keyDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'); const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'));
// Path to user certificate. // Path to user certificate.
const certPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem'); const certPath = envOrDefault('CERT_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem'));
// Path to peer tls certificate. // Path to peer tls certificate.
const tlsCertPath = path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'));
// Gateway peer endpoint. // Gateway peer endpoint.
const peerEndpoint = 'localhost:7051'; const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
// Gateway peer SSL host name override.
const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.example.com');
const utf8Decoder = new TextDecoder(); const utf8Decoder = new TextDecoder();
const assetId = `asset${Date.now()}`; const assetId = `asset${Date.now()}`;
async function main(): Promise<void> { async function main(): Promise<void> {
await displayInputParameters();
// The gRPC client connection should be shared by all Gateway connections to this endpoint. // The gRPC client connection should be shared by all Gateway connections to this endpoint.
const client = await newGrpcConnection(); const client = await newGrpcConnection();
@ -86,13 +92,16 @@ async function main(): Promise<void> {
} }
} }
main().catch(error => console.error('******** FAILED to run the application:', error)); main().catch(error => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});
async function newGrpcConnection(): Promise<grpc.Client> { async function newGrpcConnection(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath); const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, { return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': 'peer0.org1.example.com', 'grpc.ssl_target_name_override': peerHostAlias,
}); });
} }
@ -205,3 +214,25 @@ async function updateNonExistentAsset(contract: Contract): Promise<void>{
console.log('*** Successfully caught the error: \n', error); console.log('*** Successfully caught the error: \n', error);
} }
} }
/**
* envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined.
*/
function envOrDefault(key: string, defaultValue: string): string {
return process.env[key] || defaultValue;
}
/**
* displayInputParameters() will print the global scope parameters used by the main driver routine.
*/
async function displayInputParameters(): Promise<void> {
console.log(`channelName: ${channelName}`);
console.log(`chaincodeName: ${chaincodeName}`);
console.log(`mspId: ${mspId}`);
console.log(`cryptoPath: ${cryptoPath}`);
console.log(`keyDirectoryPath: ${keyDirectoryPath}`);
console.log(`certPath: ${certPath}`);
console.log(`tlsCertPath: ${tlsCertPath}`);
console.log(`peerEndpoint: ${peerEndpoint}`);
console.log(`peerHostAlias: ${peerHostAlias}`);
}

View file

@ -18,9 +18,8 @@ ext {
} }
repositories { repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here. // You can declare any Maven/Ivy/file repository here.
jcenter() mavenCentral()
} }
dependencies { dependencies {

View file

@ -1,6 +1,5 @@
# the first stage # the first stage
FROM gradle:jdk11 AS GRADLE_BUILD FROM gradle:jdk11 AS GRADLE_BUILD
ARG CC_SERVER_PORT
# copy the build.gradle and src code to the container # copy the build.gradle and src code to the container
COPY src/ src/ COPY src/ src/
@ -12,6 +11,7 @@ RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
# the second stage of our build just needs the compiled files # the second stage of our build just needs the compiled files
FROM openjdk:11-jre FROM openjdk:11-jre
ARG CC_SERVER_PORT=9999
# Setup tini to work better handle signals # Setup tini to work better handle signals
ENV TINI_VERSION v0.19.0 ENV TINI_VERSION v0.19.0

View file

@ -24,10 +24,7 @@ dependencies {
} }
repositories { repositories {
maven { mavenCentral()
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -138,7 +138,7 @@ class AssetTransfer extends Contract {
const oldOwner = asset.Owner; const oldOwner = asset.Owner;
asset.Owner = newOwner; asset.Owner = newOwner;
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset)))); await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
return oldOwner; return oldOwner;
} }

View file

@ -2,7 +2,6 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
FROM node:16 AS builder FROM node:16 AS builder
ARG CC_SERVER_PORT
WORKDIR /usr/src/app WORKDIR /usr/src/app
@ -12,6 +11,7 @@ RUN npm ci && npm run package
FROM node:16 AS production FROM node:16 AS production
ARG CC_SERVER_PORT
# Setup tini to work better handle signals # Setup tini to work better handle signals
ENV TINI_VERSION v0.19.0 ENV TINI_VERSION v0.19.0

View file

@ -4,7 +4,7 @@ Sample REST server to demonstrate good Fabric Node SDK practices.
The REST API is only intended to work with the [basic asset transfer example](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic). The REST API is only intended to work with the [basic asset transfer example](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic).
To install the basic asset transfer chaincode on a local Fabric network, follow the [Using the Fabric test network](https://hyperledger-fabric.readthedocs.io/en/release-2.4/test_network.html) tutorial. To install the basic asset transfer chaincode on a local Fabric network, follow the [Using the Fabric test network](https://hyperledger-fabric.readthedocs.io/en/release-2.4/test_network.html) tutorial. You need to go at least as far as the step where the ledger gets initialized with assets.
## Overview ## Overview
@ -121,6 +121,7 @@ TEST_NETWORK_HOME=$HOME/fabric-samples/test-network npm run generateEnv
Start a Redis server (Redis is used to store the queue of submit transactions) Start a Redis server (Redis is used to store the queue of submit transactions)
```shell ```shell
export REDIS_PASSWORD=$(uuidgen)
npm run start:redis npm run start:redis
``` ```
@ -132,13 +133,9 @@ npm run start:dev
### Docker image ### Docker image
Alternatively, run the following commands in the `fabric-rest-sample/asset-transfer-basic/rest-api-typescript` directory to start the sample in a Docker container It's also possible to use the [published docker image](https://github.com/hyperledger/fabric-samples/pkgs/container/fabric-rest-sample) to run the sample
Build the Docker image Clone the `fabric-samples` repository and change to the `fabric-samples/asset-transfer-basic/rest-api-typescript` directory before running the following commands
```shell
docker build -t fabric-rest-sample .
```
Create a `.env` file to configure the server for the test network (make sure `TEST_NETWORK_HOME` is set to the fully qualified `test-network` directory and `AS_LOCAL_HOST` is set to `false` so that the server works inside the Docker Compose network) Create a `.env` file to configure the server for the test network (make sure `TEST_NETWORK_HOME` is set to the fully qualified `test-network` directory and `AS_LOCAL_HOST` is set to `false` so that the server works inside the Docker Compose network)
@ -151,6 +148,7 @@ TEST_NETWORK_HOME=$HOME/fabric-samples/test-network AS_LOCAL_HOST=false npm run
Start the sample REST server and Redis server Start the sample REST server and Redis server
```shell ```shell
export REDIS_PASSWORD=$(uuidgen)
docker-compose up -d docker-compose up -d
``` ```
@ -187,7 +185,7 @@ curl --include --header "X-Api-Key: ${SAMPLE_APIKEY}" --request OPTIONS http://l
### Create an asset... ### Create an asset...
```shell ```shell
curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request POST --data '{"id":"asset7","color":"red","size":42,"owner":"Jean","appraisedValue":101}' http://localhost:3000/api/assets curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request POST --data '{"ID":"asset7","Color":"red","Size":42,"Owner":"Jean","AppraisedValue":101}' http://localhost:3000/api/assets
``` ```
The response should include a `jobId` which you can use to check the job status in next step The response should include a `jobId` which you can use to check the job status in next step
@ -239,13 +237,13 @@ You should see the newly created asset, for example
### Update an asset... ### Update an asset...
```shell ```shell
curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request PUT --data '{"id":"asset7","color":"red","size":11,"owner":"Jean","appraisedValue":101}' http://localhost:3000/api/assets/asset7 curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request PUT --data '{"ID":"asset7","Color":"red","Size":11,"Owner":"Jean","AppraisedValue":101}' http://localhost:3000/api/assets/asset7
``` ```
### Transfer an asset... ### Transfer an asset...
```shell ```shell
curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request PATCH --data '[{"op":"replace","path":"/owner","value":"Ashleigh"}]' http://localhost:3000/api/assets/asset7 curl --include --header "Content-Type: application/json" --header "X-Api-Key: ${SAMPLE_APIKEY}" --request PATCH --data '[{"op":"replace","path":"/Owner","value":"Ashleigh"}]' http://localhost:3000/api/assets/asset7
``` ```
### Delete an asset... ### Delete an asset...

View file

@ -33,11 +33,11 @@ content-type: application/json
X-Api-Key: {{api-key}} X-Api-Key: {{api-key}}
{ {
"id": "asset7", "ID": "asset7",
"color": "red", "Color": "red",
"size": 42, "Size": 42,
"owner": "Jean", "Owner": "Jean",
"appraisedValue": 101 "AppraisedValue": 101
} }
### Read job status ### Read job status
@ -62,11 +62,11 @@ content-type: application/json
X-Api-Key: {{api-key}} X-Api-Key: {{api-key}}
{ {
"id": "asset7", "ID": "asset7",
"color": "red", "Color": "red",
"size": 11, "Size": 11,
"owner": "Jean", "Owner": "Jean",
"appraisedValue": 101 "AppraisedValue": 101
} }
### Transfer asset ### Transfer asset
@ -78,7 +78,7 @@ X-Api-Key: {{api-key}}
[ [
{ {
"op": "replace", "op": "replace",
"path": "/owner", "path": "/Owner",
"value": "Ashleigh" "value": "Ashleigh"
} }
] ]

View file

@ -3,18 +3,21 @@ version: '3'
services: services:
redis: redis:
image: 'redis' image: 'redis'
command: ['--maxmemory-policy','noeviction','--requirepass','${REDIS_PASSWORD}']
ports: ports:
- 6379:6379 - 6379:6379
networks: networks:
- fabric_test - fabric_test
nodeapp: nodeapp:
image: 'fabric-rest-sample' image: 'ghcr.io/hyperledger/fabric-rest-sample:latest'
command: ['start:dotenv'] command: ['start:dotenv']
ports: ports:
- 3000:3000 - 3000:3000
env_file: env_file:
- ./.env - ./.env
environment:
- REDIS_PASSWORD
networks: networks:
- fabric_test - fabric_test

File diff suppressed because it is too large Load diff

View file

@ -58,7 +58,7 @@
"start": "node --require source-map-support/register ./dist", "start": "node --require source-map-support/register ./dist",
"start:dotenv": "node --require source-map-support/register --require dotenv/config ./dist", "start:dotenv": "node --require source-map-support/register --require dotenv/config ./dist",
"start:dev": "node --require source-map-support/register --require dotenv/config ./dist | pino-pretty", "start:dev": "node --require source-map-support/register --require dotenv/config ./dist | pino-pretty",
"start:redis": "docker run -p 6379:6379 --name fabric-sample-redis -d redis --maxmemory-policy noeviction", "start:redis": "docker run -p 6379:6379 --name fabric-sample-redis -d redis --maxmemory-policy noeviction --requirepass \"${REDIS_PASSWORD}\"",
"test": "jest" "test": "jest"
}, },
"author": "Hyperledger", "author": "Hyperledger",

View file

@ -208,11 +208,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.post('/api/assets') .post('/api/assets')
.send({ .send({
identifier: 'asset3', wrongidfield: 'asset3',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(400); expect(response.statusCode).toEqual(400);
@ -227,7 +227,7 @@ describe('Asset Transfer Besic REST API', () => {
{ {
location: 'body', location: 'body',
msg: 'must be a string', msg: 'must be a string',
param: 'id', param: 'ID',
}, },
], ],
message: 'Invalid request body', message: 'Invalid request body',
@ -239,11 +239,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.post('/api/assets') .post('/api/assets')
.send({ .send({
id: 'asset3', ID: 'asset3',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(202); expect(response.statusCode).toEqual(202);
@ -401,11 +401,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.put('/api/assets/asset1') .put('/api/assets/asset1')
.send({ .send({
id: 'asset3', ID: 'asset3',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY'); .set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
expect(response.statusCode).toEqual(401); expect(response.statusCode).toEqual(401);
@ -424,11 +424,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.put('/api/assets/asset1') .put('/api/assets/asset1')
.send({ .send({
id: 'asset2', ID: 'asset2',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(400); expect(response.statusCode).toEqual(400);
@ -448,11 +448,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.put('/api/assets/asset1') .put('/api/assets/asset1')
.send({ .send({
identifier: 'asset1', wrongID: 'asset1',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(400); expect(response.statusCode).toEqual(400);
@ -467,7 +467,7 @@ describe('Asset Transfer Besic REST API', () => {
{ {
location: 'body', location: 'body',
msg: 'must be a string', msg: 'must be a string',
param: 'id', param: 'ID',
}, },
], ],
message: 'Invalid request body', message: 'Invalid request body',
@ -479,11 +479,11 @@ describe('Asset Transfer Besic REST API', () => {
const response = await request(app) const response = await request(app)
.put('/api/assets/asset1') .put('/api/assets/asset1')
.send({ .send({
id: 'asset1', ID: 'asset1',
color: 'red', Color: 'red',
size: 5, Size: 5,
owner: 'Brad', Owner: 'Brad',
appraisedValue: 400, AppraisedValue: 400,
}) })
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(202); expect(response.statusCode).toEqual(202);
@ -501,7 +501,7 @@ describe('Asset Transfer Besic REST API', () => {
it('PATCH should respond with 401 unauthorized json when an invalid API key is specified', async () => { it('PATCH should respond with 401 unauthorized json when an invalid API key is specified', async () => {
const response = await request(app) const response = await request(app)
.patch('/api/assets/asset1') .patch('/api/assets/asset1')
.send([{ op: 'replace', path: '/owner', value: 'Ashleigh' }]) .send([{ op: 'replace', path: '/Owner', value: 'Ashleigh' }])
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY'); .set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
expect(response.statusCode).toEqual(401); expect(response.statusCode).toEqual(401);
expect(response.header).toHaveProperty( expect(response.header).toHaveProperty(
@ -531,7 +531,7 @@ describe('Asset Transfer Besic REST API', () => {
errors: [ errors: [
{ {
location: 'body', location: 'body',
msg: "path must be '/owner'", msg: "path must be '/Owner'",
param: '[0].path', param: '[0].path',
value: '/color', value: '/color',
}, },
@ -544,7 +544,7 @@ describe('Asset Transfer Besic REST API', () => {
it('PATCH should respond with 202 accepted json', async () => { it('PATCH should respond with 202 accepted json', async () => {
const response = await request(app) const response = await request(app)
.patch('/api/assets/asset1') .patch('/api/assets/asset1')
.send([{ op: 'replace', path: '/owner', value: 'Ashleigh' }]) .send([{ op: 'replace', path: '/Owner', value: 'Ashleigh' }])
.set('X-Api-Key', 'ORG1MOCKAPIKEY'); .set('X-Api-Key', 'ORG1MOCKAPIKEY');
expect(response.statusCode).toEqual(202); expect(response.statusCode).toEqual(202);
expect(response.header).toHaveProperty( expect(response.header).toHaveProperty(

View file

@ -35,7 +35,6 @@ export const assetsRouter = express.Router();
assetsRouter.get('/', async (req: Request, res: Response) => { assetsRouter.get('/', async (req: Request, res: Response) => {
logger.debug('Get all assets request received'); logger.debug('Get all assets request received');
try { try {
const mspId = req.user as string; const mspId = req.user as string;
const contract = req.app.locals[mspId]?.assetContract as Contract; const contract = req.app.locals[mspId]?.assetContract as Contract;
@ -59,11 +58,11 @@ assetsRouter.get('/', async (req: Request, res: Response) => {
assetsRouter.post( assetsRouter.post(
'/', '/',
body().isObject().withMessage('body must contain an asset object'), body().isObject().withMessage('body must contain an asset object'),
body('id', 'must be a string').notEmpty(), body('ID', 'must be a string').notEmpty(),
body('color', 'must be a string').notEmpty(), body('Color', 'must be a string').notEmpty(),
body('size', 'must be a number').isNumeric(), body('Size', 'must be a number').isNumeric(),
body('owner', 'must be a string').notEmpty(), body('Owner', 'must be a string').notEmpty(),
body('appraisedValue', 'must be a number').isNumeric(), body('AppraisedValue', 'must be a number').isNumeric(),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
logger.debug(req.body, 'Create asset request received'); logger.debug(req.body, 'Create asset request received');
@ -79,7 +78,7 @@ assetsRouter.post(
} }
const mspId = req.user as string; const mspId = req.user as string;
const assetId = req.body.id; const assetId = req.body.ID;
try { try {
const submitQueue = req.app.locals.jobq as Queue; const submitQueue = req.app.locals.jobq as Queue;
@ -88,10 +87,10 @@ assetsRouter.post(
mspId, mspId,
'CreateAsset', 'CreateAsset',
assetId, assetId,
req.body.color, req.body.Color,
req.body.size, req.body.Size,
req.body.owner, req.body.Owner,
req.body.appraisedValue req.body.AppraisedValue
); );
return res.status(ACCEPTED).json({ return res.status(ACCEPTED).json({
@ -190,11 +189,11 @@ assetsRouter.get('/:assetId', async (req: Request, res: Response) => {
assetsRouter.put( assetsRouter.put(
'/:assetId', '/:assetId',
body().isObject().withMessage('body must contain an asset object'), body().isObject().withMessage('body must contain an asset object'),
body('id', 'must be a string').notEmpty(), body('ID', 'must be a string').notEmpty(),
body('color', 'must be a string').notEmpty(), body('Color', 'must be a string').notEmpty(),
body('size', 'must be a number').isNumeric(), body('Size', 'must be a number').isNumeric(),
body('owner', 'must be a string').notEmpty(), body('Owner', 'must be a string').notEmpty(),
body('appraisedValue', 'must be a number').isNumeric(), body('AppraisedValue', 'must be a number').isNumeric(),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
logger.debug(req.body, 'Update asset request received'); logger.debug(req.body, 'Update asset request received');
@ -209,7 +208,7 @@ assetsRouter.put(
}); });
} }
if (req.params.assetId != req.body.id) { if (req.params.assetId != req.body.ID) {
return res.status(BAD_REQUEST).json({ return res.status(BAD_REQUEST).json({
status: getReasonPhrase(BAD_REQUEST), status: getReasonPhrase(BAD_REQUEST),
reason: 'ASSET_ID_MISMATCH', reason: 'ASSET_ID_MISMATCH',
@ -263,7 +262,7 @@ assetsRouter.patch(
}) })
.withMessage('body must contain an array with a single patch operation'), .withMessage('body must contain an array with a single patch operation'),
body('*.op', "operation must be 'replace'").equals('replace'), body('*.op', "operation must be 'replace'").equals('replace'),
body('*.path', "path must be '/owner'").equals('/owner'), body('*.path', "path must be '/Owner'").equals('/Owner'),
body('*.value', 'must be a string').isString(), body('*.value', 'must be a string').isString(),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
logger.debug(req.body, 'Transfer asset request received'); logger.debug(req.body, 'Transfer asset request received');

View file

@ -20,7 +20,7 @@ export const ORG2 = 'Org2';
export const JOB_QUEUE_NAME = 'submit'; export const JOB_QUEUE_NAME = 'submit';
/* /**
* Log level for the REST server * Log level for the REST server
*/ */
export const logLevel = env export const logLevel = env
@ -28,7 +28,7 @@ export const logLevel = env
.default('info') .default('info')
.asEnum(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']); .asEnum(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']);
/* /**
* The port to start the REST server on * The port to start the REST server on
*/ */
export const port = env export const port = env
@ -37,7 +37,7 @@ export const port = env
.example('3000') .example('3000')
.asPortNumber(); .asPortNumber();
/* /**
* The type of backoff to use for retrying failed submit jobs * The type of backoff to use for retrying failed submit jobs
*/ */
export const submitJobBackoffType = env export const submitJobBackoffType = env
@ -45,7 +45,7 @@ export const submitJobBackoffType = env
.default('fixed') .default('fixed')
.asEnum(['fixed', 'exponential']); .asEnum(['fixed', 'exponential']);
/* /**
* Backoff delay for retrying failed submit jobs in milliseconds * Backoff delay for retrying failed submit jobs in milliseconds
*/ */
export const submitJobBackoffDelay = env export const submitJobBackoffDelay = env
@ -54,7 +54,7 @@ export const submitJobBackoffDelay = env
.example('3000') .example('3000')
.asIntPositive(); .asIntPositive();
/* /**
* The total number of attempts to try a submit job until it completes * The total number of attempts to try a submit job until it completes
*/ */
export const submitJobAttempts = env export const submitJobAttempts = env
@ -63,7 +63,7 @@ export const submitJobAttempts = env
.example('5') .example('5')
.asIntPositive(); .asIntPositive();
/* /**
* The maximum number of submit jobs that can be processed in parallel * The maximum number of submit jobs that can be processed in parallel
*/ */
export const submitJobConcurrency = env export const submitJobConcurrency = env
@ -72,7 +72,7 @@ export const submitJobConcurrency = env
.example('5') .example('5')
.asIntPositive(); .asIntPositive();
/* /**
* The number of completed submit jobs to keep * The number of completed submit jobs to keep
*/ */
export const maxCompletedSubmitJobs = env export const maxCompletedSubmitJobs = env
@ -81,7 +81,7 @@ export const maxCompletedSubmitJobs = env
.example('1000') .example('1000')
.asIntPositive(); .asIntPositive();
/* /**
* The number of failed submit jobs to keep * The number of failed submit jobs to keep
*/ */
export const maxFailedSubmitJobs = env export const maxFailedSubmitJobs = env
@ -90,7 +90,7 @@ export const maxFailedSubmitJobs = env
.example('1000') .example('1000')
.asIntPositive(); .asIntPositive();
/* /**
* Whether to initialise a scheduler for the submit job queue * Whether to initialise a scheduler for the submit job queue
* There must be at least on queue scheduler to handle retries and you may want * There must be at least on queue scheduler to handle retries and you may want
* more than one for redundancy * more than one for redundancy
@ -101,7 +101,7 @@ export const submitJobQueueScheduler = env
.example('true') .example('true')
.asBoolStrict(); .asBoolStrict();
/* /**
* Whether to convert discovered host addresses to be 'localhost' * Whether to convert discovered host addresses to be 'localhost'
* This should be set to 'true' when running a docker composed fabric network on the * This should be set to 'true' when running a docker composed fabric network on the
* local system, e.g. using the test network; otherwise should it should be 'false' * local system, e.g. using the test network; otherwise should it should be 'false'
@ -112,7 +112,7 @@ export const asLocalhost = env
.example('true') .example('true')
.asBoolStrict(); .asBoolStrict();
/* /**
* The Org1 MSP ID * The Org1 MSP ID
*/ */
export const mspIdOrg1 = env export const mspIdOrg1 = env
@ -121,7 +121,7 @@ export const mspIdOrg1 = env
.example(`${ORG1}MSP`) .example(`${ORG1}MSP`)
.asString(); .asString();
/* /**
* The Org2 MSP ID * The Org2 MSP ID
*/ */
export const mspIdOrg2 = env export const mspIdOrg2 = env
@ -130,7 +130,7 @@ export const mspIdOrg2 = env
.example(`${ORG2}MSP`) .example(`${ORG2}MSP`)
.asString(); .asString();
/* /**
* Name of the channel which the basic asset sample chaincode has been installed on * Name of the channel which the basic asset sample chaincode has been installed on
*/ */
export const channelName = env export const channelName = env
@ -139,7 +139,7 @@ export const channelName = env
.example('mychannel') .example('mychannel')
.asString(); .asString();
/* /**
* Name used to install the basic asset sample * Name used to install the basic asset sample
*/ */
export const chaincodeName = env export const chaincodeName = env
@ -148,7 +148,7 @@ export const chaincodeName = env
.example('basic') .example('basic')
.asString(); .asString();
/* /**
* The transaction submit timeout in seconds for commit notification to complete * The transaction submit timeout in seconds for commit notification to complete
*/ */
export const commitTimeout = env export const commitTimeout = env
@ -157,7 +157,7 @@ export const commitTimeout = env
.example('300') .example('300')
.asIntPositive(); .asIntPositive();
/* /**
* The transaction submit timeout in seconds for the endorsement to complete * The transaction submit timeout in seconds for the endorsement to complete
*/ */
export const endorseTimeout = env export const endorseTimeout = env
@ -166,7 +166,7 @@ export const endorseTimeout = env
.example('30') .example('30')
.asIntPositive(); .asIntPositive();
/* /**
* The transaction query timeout in seconds * The transaction query timeout in seconds
*/ */
export const queryTimeout = env export const queryTimeout = env
@ -175,7 +175,7 @@ export const queryTimeout = env
.example('3') .example('3')
.asIntPositive(); .asIntPositive();
/* /**
* The Org1 connection profile JSON * The Org1 connection profile JSON
*/ */
export const connectionProfileOrg1 = env export const connectionProfileOrg1 = env
@ -186,7 +186,7 @@ export const connectionProfileOrg1 = env
) )
.asJsonObject() as Record<string, unknown>; .asJsonObject() as Record<string, unknown>;
/* /**
* Certificate for an Org1 identity to evaluate and submit transactions * Certificate for an Org1 identity to evaluate and submit transactions
*/ */
export const certificateOrg1 = env export const certificateOrg1 = env
@ -195,7 +195,7 @@ export const certificateOrg1 = env
.example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"') .example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"')
.asString(); .asString();
/* /**
* Private key for an Org1 identity to evaluate and submit transactions * Private key for an Org1 identity to evaluate and submit transactions
*/ */
export const privateKeyOrg1 = env export const privateKeyOrg1 = env
@ -204,7 +204,7 @@ export const privateKeyOrg1 = env
.example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"') .example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"')
.asString(); .asString();
/* /**
* The Org2 connection profile JSON * The Org2 connection profile JSON
*/ */
export const connectionProfileOrg2 = env export const connectionProfileOrg2 = env
@ -215,7 +215,7 @@ export const connectionProfileOrg2 = env
) )
.asJsonObject() as Record<string, unknown>; .asJsonObject() as Record<string, unknown>;
/* /**
* Certificate for an Org2 identity to evaluate and submit transactions * Certificate for an Org2 identity to evaluate and submit transactions
*/ */
export const certificateOrg2 = env export const certificateOrg2 = env
@ -224,7 +224,7 @@ export const certificateOrg2 = env
.example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"') .example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"')
.asString(); .asString();
/* /**
* Private key for an Org2 identity to evaluate and submit transactions * Private key for an Org2 identity to evaluate and submit transactions
*/ */
export const privateKeyOrg2 = env export const privateKeyOrg2 = env
@ -233,7 +233,7 @@ export const privateKeyOrg2 = env
.example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"') .example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"')
.asString(); .asString();
/* /**
* The host the Redis server is running on * The host the Redis server is running on
*/ */
export const redisHost = env export const redisHost = env
@ -242,7 +242,7 @@ export const redisHost = env
.example('localhost') .example('localhost')
.asString(); .asString();
/* /**
* The port the Redis server is running on * The port the Redis server is running on
*/ */
export const redisPort = env export const redisPort = env
@ -251,7 +251,7 @@ export const redisPort = env
.example('6379') .example('6379')
.asPortNumber(); .asPortNumber();
/* /**
* Username for the Redis server * Username for the Redis server
*/ */
export const redisUsername = env export const redisUsername = env
@ -259,12 +259,12 @@ export const redisUsername = env
.example('fabric') .example('fabric')
.asString(); .asString();
/* /**
* Password for the Redis server * Password for the Redis server
*/ */
export const redisPassword = env.get('REDIS_PASSWORD').asString(); export const redisPassword = env.get('REDIS_PASSWORD').asString();
/* /**
* API key for Org1 * API key for Org1
* Specify this API key with the X-Api-Key header to use the Org1 connection profile and credentials * Specify this API key with the X-Api-Key header to use the Org1 connection profile and credentials
*/ */
@ -274,7 +274,7 @@ export const org1ApiKey = env
.example('123') .example('123')
.asString(); .asString();
/* /**
* API key for Org2 * API key for Org2
* Specify this API key with the X-Api-Key header to use the Org2 connection profile and credentials * Specify this API key with the X-Api-Key header to use the Org2 connection profile and credentials
*/ */

View file

@ -8,7 +8,7 @@
import { TimeoutError, TransactionError } from 'fabric-network'; import { TimeoutError, TransactionError } from 'fabric-network';
import { logger } from './logger'; import { logger } from './logger';
/* /**
* Base type for errors from the smart contract. * Base type for errors from the smart contract.
* *
* These errors will not be retried. * These errors will not be retried.
@ -25,7 +25,7 @@ export class ContractError extends Error {
} }
} }
/* /**
* Represents the error which occurs when the transaction being submitted or * Represents the error which occurs when the transaction being submitted or
* evaluated is not implemented in a smart contract. * evaluated is not implemented in a smart contract.
*/ */
@ -38,7 +38,7 @@ export class TransactionNotFoundError extends ContractError {
} }
} }
/* /**
* Represents the error which occurs in the basic asset transfer smart contract * Represents the error which occurs in the basic asset transfer smart contract
* implementation when an asset already exists. * implementation when an asset already exists.
*/ */
@ -51,7 +51,7 @@ export class AssetExistsError extends ContractError {
} }
} }
/* /**
* Represents the error which occurs in the basic asset transfer smart contract * Represents the error which occurs in the basic asset transfer smart contract
* implementation when an asset does not exist. * implementation when an asset does not exist.
*/ */
@ -64,26 +64,30 @@ export class AssetNotFoundError extends ContractError {
} }
} }
/* /**
* Enumeration of possible retry actions. * Enumeration of possible retry actions.
*
* WithExistingTransactionId - transactions should be retried using the same
* transaction ID to protect against duplicate transactions being committed if
* a timeout error occurs
*
* WithNewTransactionId - transactions which could not be committed due to
* other errors require a new transaction ID when retrying
*
* None - transactions that failed due to a duplicate transaction error, or
* errors from the smart contract, should not be retried
*/ */
export enum RetryAction { export enum RetryAction {
/**
* Transactions should be retried using the same transaction ID to protect
* against duplicate transactions being committed if a timeout error occurs
*/
WithExistingTransactionId, WithExistingTransactionId,
/**
* Transactions which could not be committed due to other errors require a
* new transaction ID when retrying
*/
WithNewTransactionId, WithNewTransactionId,
/**
* Transactions that failed due to a duplicate transaction error, or errors
* from the smart contract, should not be retried
*/
None, None,
} }
/* /**
* Get the required transaction retry action for an error. * Get the required transaction retry action for an error.
* *
* For this sample transactions are considered retriable if they fail with any * For this sample transactions are considered retriable if they fail with any
@ -92,11 +96,11 @@ export enum RetryAction {
* *
* You might decide to retry transactions which fail with specific errors * You might decide to retry transactions which fail with specific errors
* instead, for example: * instead, for example:
* MVCC_READ_CONFLICT * - MVCC_READ_CONFLICT
* PHANTOM_READ_CONFLICT * - PHANTOM_READ_CONFLICT
* ENDORSEMENT_POLICY_FAILURE * - ENDORSEMENT_POLICY_FAILURE
* CHAINCODE_VERSION_CONFLICT * - CHAINCODE_VERSION_CONFLICT
* EXPIRED_CHAINCODE * - EXPIRED_CHAINCODE
*/ */
export const getRetryAction = (err: unknown): RetryAction => { export const getRetryAction = (err: unknown): RetryAction => {
if (isDuplicateTransactionError(err) || err instanceof ContractError) { if (isDuplicateTransactionError(err) || err instanceof ContractError) {
@ -108,7 +112,7 @@ export const getRetryAction = (err: unknown): RetryAction => {
return RetryAction.WithNewTransactionId; return RetryAction.WithNewTransactionId;
}; };
/* /**
* Type guard to make catching unknown errors easier * Type guard to make catching unknown errors easier
*/ */
export const isErrorLike = (err: unknown): err is Error => { export const isErrorLike = (err: unknown): err is Error => {
@ -122,7 +126,7 @@ export const isErrorLike = (err: unknown): err is Error => {
); );
}; };
/* /**
* Checks whether an error was caused by a duplicate transaction. * Checks whether an error was caused by a duplicate transaction.
* *
* This is ...painful. * This is ...painful.
@ -155,13 +159,13 @@ export const isDuplicateTransactionError = (err: unknown): boolean => {
return isDuplicate === true; return isDuplicate === true;
}; };
/* /**
* Matches asset already exists error strings from the asset contract * Matches asset already exists error strings from the asset contract
* *
* The regex needs to match the following error messages: * The regex needs to match the following error messages:
* "the asset %s already exists" * - "the asset %s already exists"
* "The asset ${id} already exists" * - "The asset ${id} already exists"
* "Asset %s already exists" * - "Asset %s already exists"
*/ */
const matchAssetAlreadyExistsMessage = (message: string): string | null => { const matchAssetAlreadyExistsMessage = (message: string): string | null => {
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g; const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
@ -178,13 +182,13 @@ const matchAssetAlreadyExistsMessage = (message: string): string | null => {
return null; return null;
}; };
/* /**
* Matches asset does not exist error strings from the asset contract * Matches asset does not exist error strings from the asset contract
* *
* The regex needs to match the following error messages: * The regex needs to match the following error messages:
* "the asset %s does not exist" * - "the asset %s does not exist"
* "The asset ${id} does not exist" * - "The asset ${id} does not exist"
* "Asset %s does not exist" * - "Asset %s does not exist"
*/ */
const matchAssetDoesNotExistMessage = (message: string): string | null => { const matchAssetDoesNotExistMessage = (message: string): string | null => {
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g; const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
@ -201,12 +205,12 @@ const matchAssetDoesNotExistMessage = (message: string): string | null => {
return null; return null;
}; };
/* /**
* Matches transaction does not exist error strings from the contract API * Matches transaction does not exist error strings from the contract API
* *
* The regex needs to match the following error messages: * The regex needs to match the following error messages:
* "Failed to get transaction with id %s, error Entry not found in index" * - "Failed to get transaction with id %s, error Entry not found in index"
* "Failed to get transaction with id %s, error no such transaction ID [%s] in index" * - "Failed to get transaction with id %s, error no such transaction ID [%s] in index"
*/ */
const matchTransactionDoesNotExistMessage = ( const matchTransactionDoesNotExistMessage = (
message: string message: string
@ -228,11 +232,12 @@ const matchTransactionDoesNotExistMessage = (
return null; return null;
}; };
/* /**
* Handles errors from evaluating and submitting transactions. * Handles errors from evaluating and submitting transactions.
* *
* Smart contract errors from the the basic asset transfer samples do not use * Smart contract errors from the basic asset transfer samples do not use
* error codes so matching strings is the only option, which is not ideal. * error codes so matching strings is the only option, which is not ideal.
*
* Note: the error message text is not the same for the Go, Java, and * Note: the error message text is not the same for the Go, Java, and
* Javascript implementations of the chaincode! * Javascript implementations of the chaincode!
*/ */

View file

@ -18,7 +18,7 @@ import { logger } from './logger';
import { handleError } from './errors'; import { handleError } from './errors';
import * as protos from 'fabric-protos'; import * as protos from 'fabric-protos';
/* /**
* Creates an in memory wallet to hold credentials for an Org1 and Org2 user * Creates an in memory wallet to hold credentials for an Org1 and Org2 user
* *
* In this sample there is a single user for each MSP ID to demonstrate how * In this sample there is a single user for each MSP ID to demonstrate how
@ -55,7 +55,7 @@ export const createWallet = async (): Promise<Wallet> => {
return wallet; return wallet;
}; };
/* /**
* Create a Gateway connection * Create a Gateway connection
* *
* Gateway instances can and should be reused rather than connecting to submit every transaction * Gateway instances can and should be reused rather than connecting to submit every transaction
@ -89,7 +89,7 @@ export const createGateway = async (
return gateway; return gateway;
}; };
/* /**
* Get the network which the asset transfer sample chaincode is running on * Get the network which the asset transfer sample chaincode is running on
* *
* In addion to getting the contract, the network will also be used to * In addion to getting the contract, the network will also be used to
@ -100,7 +100,7 @@ export const getNetwork = async (gateway: Gateway): Promise<Network> => {
return network; return network;
}; };
/* /**
* Get the asset transfer sample contract and the qscc system contract * Get the asset transfer sample contract and the qscc system contract
* *
* The system contract is used for the liveness REST endpoint * The system contract is used for the liveness REST endpoint
@ -113,7 +113,7 @@ export const getContracts = async (
return { assetContract, qsccContract }; return { assetContract, qsccContract };
}; };
/* /**
* Evaluate a transaction and handle any errors * Evaluate a transaction and handle any errors
*/ */
export const evatuateTransaction = async ( export const evatuateTransaction = async (
@ -137,7 +137,7 @@ export const evatuateTransaction = async (
} }
}; };
/* /**
* Submit a transaction and handle any errors * Submit a transaction and handle any errors
*/ */
export const submitTransaction = async ( export const submitTransaction = async (
@ -159,7 +159,7 @@ export const submitTransaction = async (
} }
}; };
/* /**
* Get the validation code of the specified transaction * Get the validation code of the specified transaction
*/ */
export const getTransactionValidationCode = async ( export const getTransactionValidationCode = async (
@ -181,7 +181,7 @@ export const getTransactionValidationCode = async (
return validationCode; return validationCode;
}; };
/* /**
* Get the current block height * Get the current block height
* *
* This example of using a system contract is used for the liveness REST * This example of using a system contract is used for the liveness REST

View file

@ -52,7 +52,7 @@ const connection: ConnectionOptions = {
password: config.redisPassword, password: config.redisPassword,
}; };
/* /**
* Set up the queue for submit jobs * Set up the queue for submit jobs
*/ */
export const initJobQueue = (): Queue => { export const initJobQueue = (): Queue => {
@ -72,7 +72,7 @@ export const initJobQueue = (): Queue => {
return submitQueue; return submitQueue;
}; };
/* /**
* Set up a worker to process submit jobs on the queue, using the * Set up a worker to process submit jobs on the queue, using the
* processSubmitTransactionJob function below * processSubmitTransactionJob function below
*/ */
@ -104,7 +104,7 @@ export const initJobQueueWorker = (app: Application): Worker => {
return worker; return worker;
}; };
/* /**
* Process a submit transaction request from the job queue * Process a submit transaction request from the job queue
* *
* The job will be retried if this function throws an error * The job will be retried if this function throws an error
@ -209,7 +209,7 @@ export const processSubmitTransactionJob = async (
} }
}; };
/* /**
* Set up a scheduler for the submit job queue * Set up a scheduler for the submit job queue
* *
* This manages stalled and delayed jobs and is required for retries with backoff * This manages stalled and delayed jobs and is required for retries with backoff
@ -226,7 +226,7 @@ export const initJobQueueScheduler = (): QueueScheduler => {
return queueScheduler; return queueScheduler;
}; };
/* /**
* Helper to add a new submit transaction job to the queue * Helper to add a new submit transaction job to the queue
*/ */
export const addSubmitTransactionJob = async ( export const addSubmitTransactionJob = async (
@ -250,7 +250,7 @@ export const addSubmitTransactionJob = async (
return job.id; return job.id;
}; };
/* /**
* Helper to update the data for an existing job * Helper to update the data for an existing job
*/ */
export const updateJobData = async ( export const updateJobData = async (
@ -274,7 +274,7 @@ export const updateJobData = async (
await job.update(newData); await job.update(newData);
}; };
/* /**
* Gets a job summary * Gets a job summary
* *
* This function is used for the jobs REST endpoint * This function is used for the jobs REST endpoint
@ -325,7 +325,7 @@ export const getJobSummary = async (
return jobSummary; return jobSummary;
}; };
/* /**
* Get the current job counts * Get the current job counts
* *
* This function is used for the liveness REST endpoint * This function is used for the liveness REST endpoint

View file

@ -9,7 +9,7 @@ import IORedis, { Redis, RedisOptions } from 'ioredis';
import * as config from './config'; import * as config from './config';
import { logger } from './logger'; import { logger } from './logger';
/* /**
* Check whether the maxmemory-policy config is set to noeviction * Check whether the maxmemory-policy config is set to noeviction
* *
* BullMQ requires this setting in redis * BullMQ requires this setting in redis

View file

@ -19,6 +19,8 @@ const { BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND } = StatusCodes;
export const createServer = async (): Promise<Application> => { export const createServer = async (): Promise<Application> => {
const app = express(); const app = express();
// Remember for production usage, to check any TLS or CORS requirements
app.use( app.use(
pinoMiddleware({ pinoMiddleware({
logger, logger,

View file

@ -1,90 +1,80 @@
# Asset Transfer Events Sample # Asset transfer events sample
The asset transfer events sample demonstrates chaincode events send/receive The asset transfer events sample demonstrates:
and the receive of block events. The chaincode events are set by your
chaincode which adds the event data to the transaction and are sent when the - Emitting chaincode events from smart contract transaction functions.
transaction is committed to the ledger. The block events are published when - Receiving chaincode events in a client application.
a block is committed to the ledger, containing all the transaction details - Replaying previous chaincode events in a client application.
within that block.
Events are published when a block is committed to the ledger.
For more information about event services on per-channel basis, visit the For more information about event services on per-channel basis, visit the
[Channel-based event service](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) [Channel-based event service](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html)
page in the Fabric documentation. page in the Fabric documentation.
## About the Sample ## About the sample
This sample includes chaincodes and application code in multiple languages. This sample includes smart contract and application code in multiple languages. In a use-case similar to basic asset transfer (see [asset-transfer-basic](../asset-transfer-basic) folder) this sample shows sending and receiving of events during create / update / delete of an asset, and during transfer of an asset to a new owner.
In a use-case similar to basic asset transfer ( see `../asset-transfer-basic` folder)
this sample shows sending and receiving of events during create/update/delete of an asset
and during transfer of an asset to a new owner.
### Application ### Application
The application demonstrates this, using two types of listeners in subsequent sections of `main` function:
1. Contract Listener: listen for events in a specific Contract
- How to register a contract listener in an application, for chaincode events
- How to get the chaincode event name and value from the chaincode event
- How to retrieve the transaction and block information from the chaincode event
2. Block Listener: listen for block level events and parse private-data events Follow the execution flow in the client application code, and corresponding output on running the application. Pay attention to the sequence of:
- How to register a block listener for full block events
- How to retrieve the transaction and block information from the block event
- How to register to receive private data associated with transactions, when registering a block listener
- How to retrieve the private data collection details from the full block event
- This section also shows how to connect to a Gateway with listener that will not listen for commit events. This may be useful when the application does not want to wait for the peer to commit blocks and notify the application.
- Transaction invocations (console output like "**--> Submit transaction**").
- Events received by the application (console output like "**<-- Chaincode event received**").
Follow the comments in `application-javascript/app.js` file, and corresponding output on running this application. Notice that events will be received by the listener after the application code submits the transaction and it is committed to the ledger, but during other application activity unrelated to the event.
Pay attention to the sequence of
- smart contract calls (console output like `--> Submit Transaction or --> Evaluate`)
- the events received at application end (console output like `<-- Contract Event Received: or <-- Block Event Received`)
The listener will be notified of an event asynchronously. Notice that events will
be posted by the listener after the application code sends the transaction (or after the
change is committed to the ledger), but during other application activity unrelated to the event.
### Smart Contract ### Smart Contract
The smart contract implements (in folder `chaincode-xyz`) following functions to support the application:
The smart contract (in folder `chaincode-xyz`) implements the following functions to support the application:
- CreateAsset - CreateAsset
- ReadAsset - ReadAsset
- UpdateAsset - UpdateAsset
- DeleteAsset - DeleteAsset
- TransferAsset - TransferAsset
Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to demonstrate the use of sending and receiving events.
demonstrate the use of sending and receiving events.
## Running the sample ## Running the sample
Like other samples, we will use the Fabric test network to deploy and run ths sample. Follow these step in order. Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
- Create the test network and a channel
```
cd test-network
./network.sh up createChannel -c mychannel -ca
```
- Deploy the chaincode (smart contract) 1. Create the test network and a channel (from the `test-network` folder).
``` ```
# to deploy javascript version ./network.sh up createChannel -c mychannel -ca
./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-events/chaincode-javascript/ -ccn asset-transfer-events-javascript ```
# or to deploy java version 1. Deploy one of the smart contract implementations (from the `test-network` folder).
./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl java -ccp ./../asset-transfer-events/chaincode-java/ -ccn asset-transfer-events-java ```
``` # To deploy the JavaScript chaincode implementation
./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
- Run the application # To deploy the Java chaincode implementation
``` ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-java/ -ccl java -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
cd application-javascript ```
npm install
# ensure this line in app.js have correct chaincode deploy name
# const chaincodeName = '...';
node app.js
```
1. Run the application (from the `asset-transfer-events` folder).
```
# To run the Go sample application
cd application-gateway-go
go run .
# To run the Typescript sample application
cd application-gateway-typescript
npm install
npm start
# To run the Java sample application
cd application-gateway-java
./gradlew run
```
## Clean up ## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
``` ```
./network.sh down ./network.sh down

View file

@ -0,0 +1,170 @@
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
const (
channelName = "mychannel"
chaincodeName = "events"
)
var now = time.Now()
var assetID = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
func main() {
clientConnection := newGrpcConnection()
defer clientConnection.Close()
id := newIdentity()
sign := newSign()
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
if err != nil {
panic(err)
}
defer gateway.Close()
network := gateway.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)
// Context used for event listening
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Listen for events emitted by subsequent transactions
startChaincodeEventListening(ctx, network)
firstBlockNumber := createAsset(contract)
updateAsset(contract)
transferAsset(contract)
deleteAsset(contract)
// Replay events from the block containing the first transaction
replayChaincodeEvents(ctx, network, firstBlockNumber)
}
func startChaincodeEventListening(ctx context.Context, network *client.Network) {
fmt.Println("\n*** Start chaincode event listening")
events, err := network.ChaincodeEvents(ctx, chaincodeName)
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
go func() {
for event := range events {
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event received: %s - %s\n", event.EventName, asset)
}
}()
}
func formatJSON(data []byte) string {
var result bytes.Buffer
if err := json.Indent(&result, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return result.String()
}
func createAsset(contract *client.Contract) uint64 {
fmt.Printf("\n--> Submit transaction: CreateAsset, %s owned by Sam with appraised value 100\n", assetID)
_, commit, err := contract.SubmitAsync("CreateAsset", client.WithArguments(assetID, "blue", "10", "Sam", "100"))
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
status, err := commit.Status()
if err != nil {
panic(fmt.Errorf("failed to get transaction commit status: %w", err))
}
if !status.Successful {
panic(fmt.Errorf("failed to commit transaction with status code %v", status.Code))
}
fmt.Println("\n*** CreateAsset committed successfully")
return status.BlockNumber
}
func updateAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: UpdateAsset, %s update appraised value to 200\n", assetID)
_, err := contract.SubmitTransaction("UpdateAsset", assetID, "blue", "10", "Sam", "200")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** UpdateAsset committed successfully")
}
func transferAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: TransferAsset, %s to Mary\n", assetID)
_, err := contract.SubmitTransaction("TransferAsset", assetID, "Mary")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** TransferAsset committed successfully")
}
func deleteAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: DeleteAsset, %s\n", assetID)
_, err := contract.SubmitTransaction("DeleteAsset", assetID)
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** DeleteAsset committed successfully")
}
func replayChaincodeEvents(ctx context.Context, network *client.Network, startBlock uint64) {
fmt.Println("\n*** Start chaincode event replay")
events, err := network.ChaincodeEvents(ctx, chaincodeName, client.WithStartBlock(startBlock))
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
for {
select {
case <-time.After(10 * time.Second):
panic(errors.New("timeout waiting for event replay"))
case event := <-events:
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event replayed: %s - %s\n", event.EventName, asset)
if event.EventName == "DeleteAsset" {
// Reached the last submitted transaction so return to stop listening for events
return
}
}
}
}

View file

@ -0,0 +1,95 @@
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"crypto/x509"
"fmt"
"io/ioutil"
"path"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
mspID = "Org1MSP"
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
peerEndpoint = "localhost:7051"
gatewayPeer = "peer0.org1.example.com"
)
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection() *grpc.ClientConn {
certificate, err := loadCertificate(tlsCertPath)
if err != nil {
panic(err)
}
certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
}
return connection
}
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity() *identity.X509Identity {
certificate, err := loadCertificate(certPath)
if err != nil {
panic(err)
}
id, err := identity.NewX509Identity(mspID, certificate)
if err != nil {
panic(err)
}
return id
}
func loadCertificate(filename string) (*x509.Certificate, error) {
certificatePEM, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read certificate file: %w", err)
}
return identity.CertificateFromPEM(certificatePEM)
}
// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign() identity.Sign {
files, err := ioutil.ReadDir(keyPath)
if err != nil {
panic(fmt.Errorf("failed to read private key directory: %w", err))
}
privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name()))
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
if err != nil {
panic(err)
}
sign, err := identity.NewPrivateKeySign(privateKey)
if err != nil {
panic(err)
}
return sign
}

View file

@ -0,0 +1,13 @@
module assetTransfer
go 1.16
require (
github.com/hyperledger/fabric-gateway v1.0.0
github.com/hyperledger/fabric-protos-go v0.0.0-20220125190318-19041b215616 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect
google.golang.org/grpc v1.44.0
)

View file

@ -0,0 +1,500 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.27.2 h1:1EyY1dsxNDUQEv0O/4TsjosHI2CgB1uo9H/v56xzTxc=
github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hyperledger/fabric v2.1.1+incompatible h1:cYYRv3vVg4kA6DmrixLxwn1nwBEUuYda8DsMwlaMKbY=
github.com/hyperledger/fabric v2.1.1+incompatible/go.mod h1:tGFAOCT696D3rG0Vofd2dyWYLySHlh0aQjf7Q1HAju0=
github.com/hyperledger/fabric-amcl v0.0.0-20200424173818-327c9e2cf77a h1:JAKZdGuUIjVmES0X31YUD7UqMR2rz/kxLluJuGvsXPk=
github.com/hyperledger/fabric-amcl v0.0.0-20200424173818-327c9e2cf77a/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE=
github.com/hyperledger/fabric-gateway v1.0.0 h1:bki1JYYdQzRGHFArxtgG4wyH6sbFNbYn3PzpdeDfjdk=
github.com/hyperledger/fabric-gateway v1.0.0/go.mod h1:uaRZyC+xzfucPqZIJpesdEsugVvChPhDxZiZmDRSFd4=
github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/hyperledger/fabric-protos-go v0.0.0-20220125190318-19041b215616 h1:CZrcDuLxBorn/xvbQl/r9kC0pniDEm0GuiBn9GgfY3c=
github.com/hyperledger/fabric-protos-go v0.0.0-20220125190318-19041b215616/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/sykesm/zap-logfmt v0.0.4 h1:U2WzRvmIWG1wDLCFY3sz8UeEmsdHQjHFNlIdmroVFaI=
github.com/sykesm/zap-logfmt v0.0.4/go.mod h1:AuBd9xQjAe3URrWT1BBDk2v2onAZHkZkWRMiYZXiZWA=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -0,0 +1,20 @@
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
// This dependency is used by the application.
implementation 'org.hyperledger.fabric:fabric-gateway:1.0.0'
implementation 'io.grpc:grpc-netty-shaded:1.42.0'
implementation 'com.google.code.gson:gson:2.8.9'
}
application {
// Define the main class for the application.
mainClass = 'App'
}

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
*/
rootProject.name = 'application-gateway-java'

View file

@ -0,0 +1,163 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.grpc.ManagedChannel;
import org.hyperledger.fabric.client.CallOption;
import org.hyperledger.fabric.client.ChaincodeEvent;
import org.hyperledger.fabric.client.ChaincodeEventsRequest;
import org.hyperledger.fabric.client.CloseableIterator;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.Network;
import org.hyperledger.fabric.client.Status;
import org.hyperledger.fabric.client.SubmitException;
import org.hyperledger.fabric.client.SubmittedTransaction;
public final class App {
private static final String channelName = "mychannel";
private static final String chaincodeName = "events";
private final Network network;
private final Contract contract;
private final String assetId = "asset" + Instant.now().toEpochMilli();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void main(final String[] args) throws Exception {
ManagedChannel grpcChannel = Connections.newGrpcConnection();
Gateway.Builder builder = Gateway.newInstance()
.identity(Connections.newIdentity())
.signer(Connections.newSigner())
.connection(grpcChannel)
.evaluateOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(CallOption.deadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(CallOption.deadlineAfter(1, TimeUnit.MINUTES));
try (Gateway gateway = builder.connect()) {
new App(gateway).run();
} finally {
grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
public App(final Gateway gateway) {
network = gateway.getNetwork(channelName);
contract = network.getContract(chaincodeName);
}
public void run() throws EndorseException, SubmitException, CommitStatusException, CommitException {
// Listen for events emitted by subsequent transactions, stopping when the try-with-resources block exits
try (CloseableIterator<ChaincodeEvent> eventSession = startChaincodeEventListening()) {
long firstBlockNumber = createAsset();
updateAsset();
transferAsset();
deleteAsset();
// Replay events from the block containing the first transaction
replayChaincodeEvents(firstBlockNumber);
}
}
private CloseableIterator<ChaincodeEvent> startChaincodeEventListening() {
System.out.println("\n*** Start chaincode event listening");
CloseableIterator<ChaincodeEvent> eventIter = network.getChaincodeEvents(chaincodeName);
CompletableFuture.runAsync(() -> {
eventIter.forEachRemaining(event -> {
String payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event received: " + event.getEventName() + " - " + payload);
});
});
return eventIter;
}
private String prettyJson(final byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private String prettyJson(final String json) {
JsonElement parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
private long createAsset() throws EndorseException, SubmitException, CommitStatusException {
System.out.println("\n--> Submit transaction: CreateAsset, " + assetId + " owned by Sam with appraised value 100");
SubmittedTransaction commit = contract.newProposal("CreateAsset")
.addArguments(assetId, "blue", "10", "Sam", "100")
.build()
.endorse()
.submitAsync();
Status status = commit.getStatus();
if (!status.isSuccessful()) {
throw new RuntimeException("failed to commit transaction with status code " + status.getCode());
}
System.out.println("\n*** CreateAsset committed successfully");
return status.getBlockNumber();
}
private void updateAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: UpdateAsset, " + assetId + " update appraised value to 200");
contract.submitTransaction("UpdateAsset", assetId, "blue", "10", "Sam", "200");
System.out.println("\n*** UpdateAsset committed successfully");
}
private void transferAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: TransferAsset, " + assetId + " to Mary");
contract.submitTransaction("TransferAsset", assetId, "Mary");
System.out.println("\n*** TransferAsset committed successfully");
}
private void deleteAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: DeleteAsset, " + assetId);
contract.submitTransaction("DeleteAsset", assetId);
System.out.println("\n*** DeleteAsset committed successfully");
}
private void replayChaincodeEvents(final long startBlock) {
System.out.println("\n*** Start chaincode event replay");
ChaincodeEventsRequest request = network.newChaincodeEventsRequest(chaincodeName)
.startBlock(startBlock)
.build();
try (CloseableIterator<ChaincodeEvent> eventIter = request.getEvents()) {
while (eventIter.hasNext()) {
ChaincodeEvent event = eventIter.next();
String payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event replayed: " + event.getEventName() + " - " + payload);
if (event.getEventName().equals("DeleteAsset")) {
// Reached the last submitted transaction so break to close the iterator and stop listening for events
break;
}
}
}
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
import org.hyperledger.fabric.client.identity.Signers;
import org.hyperledger.fabric.client.identity.X509Identity;
public final class Connections {
// Path to crypto materials.
private static final Path cryptoPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com");
// Path to user certificate.
private static final Path certPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "signcerts", "cert.pem"));
// Path to user private key directory.
private static final Path keyDirPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "keystore"));
// Path to peer tls certificate.
private static final Path tlsCertPath = cryptoPath.resolve(Paths.get("peers", "peer0.org1.example.com", "tls", "ca.crt"));
// Gateway peer end point.
private static final String peerEndpoint = "localhost:7051";
private static final String overrideAuth = "peer0.org1.example.com";
private static final String mspID = "Org1MSP";
private Connections() {
// Private constructor to prevent instantiation
}
public static ManagedChannel newGrpcConnection() throws IOException, CertificateException {
Reader tlsCertReader = Files.newBufferedReader(tlsCertPath);
X509Certificate tlsCert = Identities.readX509Certificate(tlsCertReader);
return NettyChannelBuilder.forTarget(peerEndpoint)
.sslContext(GrpcSslContexts.forClient().trustManager(tlsCert).build()).overrideAuthority(overrideAuth)
.build();
}
public static Identity newIdentity() throws IOException, CertificateException {
Reader certReader = Files.newBufferedReader(certPath);
X509Certificate certificate = Identities.readX509Certificate(certReader);
return new X509Identity(mspID, certificate);
}
public static Signer newSigner() throws IOException, InvalidKeyException {
Path keyPath = Files.list(keyDirPath)
.findFirst()
.orElseThrow();
Reader keyReader = Files.newBufferedReader(keyPath);
PrivateKey privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
}

View file

@ -0,0 +1,46 @@
{
"env": {
"node": true,
"es2020": true
},
"root": true,
"ignorePatterns": [
"dist/"
],
"extends": [
"eslint:recommended"
],
"rules": {
"indent": [
"error",
4
],
"quotes": [
"error",
"single"
]
},
"overrides": [
{
"files": [
"**/*.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
},
"project": "./tsconfig.json"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
]
}
]
}

View file

@ -0,0 +1,14 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

View file

@ -0,0 +1,32 @@
{
"name": "asset-transfer-events",
"version": "1.0.0",
"description": "Asset Transfer Events Application implemented in typeScript using fabric-gateway",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=14"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"lint": "eslint . --ext .ts",
"prepare": "npm run build",
"pretest": "npm run lint",
"start": "node dist/app.js"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.5.0",
"@hyperledger/fabric-gateway": "^1.0.0"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"eslint": "^8.4.1",
"typescript": "~4.5.2"
}
}

View file

@ -0,0 +1,157 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { ChaincodeEvent, CloseableAsyncIterable, connect, Contract, GatewayError, Network } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import { newGrpcConnection, newIdentity, newSigner } from './connect';
const channelName = 'mychannel';
const chaincodeName = 'events';
const utf8Decoder = new TextDecoder();
const now = Date.now();
const assetId = `asset${now}`;
async function main(): Promise<void> {
const client = await newGrpcConnection();
const gateway = connect({
client,
identity: await newIdentity(),
signer: await newSigner(),
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
endorseOptions: () => {
return { deadline: Date.now() + 15000 }; // 15 seconds
},
submitOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
commitStatusOptions: () => {
return { deadline: Date.now() + 60000 }; // 1 minute
},
});
let events: CloseableAsyncIterable<ChaincodeEvent> | undefined;
try {
const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
// Listen for events emitted by subsequent transactions
events = await startEventListening(network);
const firstBlockNumber = await createAsset(contract);
await updateAsset(contract);
await transferAsset(contract);
await deleteAssetByID(contract);
// Replay events from the block containing the first transaction
await replayChaincodeEvents(network,firstBlockNumber);
} finally {
events?.close();
gateway.close();
client.close();
}
}
main().catch(error => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});
async function startEventListening(network: Network): Promise<CloseableAsyncIterable<ChaincodeEvent>> {
console.log('\n*** Start chaincode event listening');
const events = await network.getChaincodeEvents(chaincodeName);
void readEvents(events); // Don't await - run asynchronously
return events;
}
async function readEvents(events: CloseableAsyncIterable<ChaincodeEvent>): Promise<void> {
try {
for await (const event of events) {
const payload = parseJson(event.payload);
console.log(`\n<-- Chaincode event received: ${event.eventName} -`, payload);
}
} catch (error: unknown) {
// Ignore the read error when events.close() is called explicitly
if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED) {
throw error;
}
}
}
function parseJson(jsonBytes: Uint8Array): unknown {
const json = utf8Decoder.decode(jsonBytes);
return JSON.parse(json);
}
async function createAsset(contract: Contract): Promise<bigint> {
console.log(`\n--> Submit Transaction: CreateAsset, ${assetId} owned by Sam with appraised value 100`);
const result = await contract.submitAsync('CreateAsset', {
arguments: [ assetId, 'blue', '10', 'Sam', '100' ],
});
const status = await result.getStatus();
if (!status.successful) {
throw new Error(`failed to commit transaction ${status.transactionId} with status code ${status.code}`);
}
console.log('\n*** CreateAsset committed successfully');
return status.blockNumber;
}
async function updateAsset(contract: Contract): Promise<void> {
console.log(`\n--> Submit transaction: UpdateAsset, ${assetId} update appraised value to 200`);
await contract.submitTransaction('UpdateAsset', assetId, 'blue', '10', 'Sam', '200');
console.log('\n*** UpdateAsset committed successfully');
}
async function transferAsset(contract: Contract): Promise<void> {
console.log(`\n--> Submit transaction: TransferAsset, ${assetId} to Mary`);
await contract.submitTransaction('TransferAsset', assetId, 'Mary');
console.log('\n*** TransferAsset committed successfully');
}
async function deleteAssetByID(contract: Contract): Promise<void>{
console.log(`\n--> Submit transaction: DeleteAsset, ${assetId}`);
await contract.submitTransaction('DeleteAsset', assetId);
console.log('\n*** DeleteAsset committed successfully');
}
async function replayChaincodeEvents(network: Network, startBlock: bigint): Promise<void> {
console.log('\n*** Start chaincode event replay');
const events = await network.getChaincodeEvents(chaincodeName, {
startBlock,
});
try {
for await (const event of events) {
const payload = parseJson(event.payload);
console.log(`\n<-- Chaincode event replayed: ${event.eventName} -`, payload);
if (event.eventName === 'DeleteAsset') {
// Reached the last submitted transaction so break to stop listening for events
break;
}
}
} finally {
events.close();
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
const mspId = 'Org1MSP';
// Path to crypto materials.
const cryptoPath = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com');
// Path to user private key directory.
const keyDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore');
// Path to user certificate.
const certPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem');
// Path to peer tls certificate.
const tlsCertPath = path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt');
// Gateway peer endpoint.
const peerEndpoint = 'localhost:7051';
export async function newGrpcConnection(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': 'peer0.org1.example.com',
});
}
export async function newIdentity(): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(): Promise<Signer> {
const files = await fs.readdir(keyDirectoryPath);
const keyPath = path.resolve(keyDirectoryPath, files[0]);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}

View file

@ -0,0 +1,18 @@
{
"extends":"@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"noImplicitAny": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
}

View file

@ -44,16 +44,16 @@
// approve, and commit the javascript chaincode, all the actions it takes // approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel. // to deploy a chaincode to a channel.
// ===> from directory test-network // ===> from directory test-network
// ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javacript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" // ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
// //
// - Be sure that node.js is installed // - Be sure that node.js is installed
// ===> from directory asset-transfer-sbe/application-javascript // ===> from directory asset-transfer-events/application-javascript
// node -v // node -v
// - npm installed code dependencies // - npm installed code dependencies
// ===> from directory asset-transfer-sbe/application-javascript // ===> from directory asset-transfer-events/application-javascript
// npm install // npm install
// - to run this test application // - to run this test application
// ===> from directory asset-transfer-sbe/application-javascript // ===> from directory asset-transfer-events/application-javascript
// node app.js // node app.js
// NOTE: If you see an error like these: // NOTE: If you see an error like these:

View file

@ -19,10 +19,7 @@ dependencies {
} }
repositories { repositories {
maven { mavenCentral()
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -18,9 +18,8 @@ ext {
} }
repositories { repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here. // You can declare any Maven/Ivy/file repository here.
jcenter() mavenCentral()
} }
dependencies { dependencies {

View file

@ -128,7 +128,7 @@ public class App {
result = contract.evaluateTransaction("QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Jin Soo\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"); result = contract.evaluateTransaction("QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Jin Soo\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}");
System.out.println("result: " + new String(result)); System.out.println("result: " + new String(result));
// Rich Query with Pagination (Only supported if CouchDB is used as state database) // Range Query with Pagination
System.out.println("\n"); System.out.println("\n");
System.out.println("Evaluate Transaction:GetAssetsByRangeWithPagination assets 3-5"); System.out.println("Evaluate Transaction:GetAssetsByRangeWithPagination assets 3-5");
result = contract.evaluateTransaction("GetAssetsByRangeWithPagination", "asset3", "asset6", "3",""); result = contract.evaluateTransaction("GetAssetsByRangeWithPagination", "asset3", "asset6", "3","");

View file

@ -181,8 +181,8 @@ async function main() {
result = await contract.evaluateTransaction('AssetExists', 'asset7'); result = await contract.evaluateTransaction('AssetExists', 'asset7');
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: TransferAsset, transfer asset(asset2) to new owner(Tom)'); console.log('\n--> Submit Transaction: TransferAsset, transfer asset(asset2) to new owner(Max)');
await contract.submitTransaction('TransferAsset', 'asset2', 'Tom'); await contract.submitTransaction('TransferAsset', 'asset2', 'Max');
console.log('*** Result: committed'); console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset2)'); console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset2)');
@ -190,15 +190,15 @@ async function main() {
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query with Pagination (Only supported if CouchDB is used as state database) // Rich Query with Pagination (Only supported if CouchDB is used as state database)
console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Tom" assets'); console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Max" assets');
result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Tom"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', ''); result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Max"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Recover the bookmark from previous query. Normally it will be inside a variable. // Recover the bookmark from previous query. Normally it will be inside a variable.
const resultJson = JSON.parse(result.toString()); const resultJson = JSON.parse(result.toString());
console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Tom" assets next page'); console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Max" assets next page');
result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Tom"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', resultJson.ResponseMetadata.Bookmark); result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Max"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', resultJson.bookmark);
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: TransferAssetByColor, transfer all yellow assets to new owner(Michel)'); console.log('\n--> Submit Transaction: TransferAssetByColor, transfer all yellow assets to new owner(Michel)');
@ -224,14 +224,14 @@ async function main() {
result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"docType":"asset","owner":"Jin Soo"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}'); result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"docType":"asset","owner":"Jin Soo"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}');
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query with Pagination (Only supported if CouchDB is used as state database) // Range Query with Pagination
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 1 of assets from asset3 to asset6 (asset3, asset4)'); console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 1 of assets from asset2 to asset6 (asset2, asset3)');
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset3', 'asset6', '2', ''); result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset2', 'asset6', '2', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query with Pagination (Only supported if CouchDB is used as state database) // Range Query with Pagination
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 2 of assets from asset3 to asset6 (asset4, asset5)'); console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 2 of assets from asset2 to asset6 (asset4, asset5)');
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset3', 'asset6', '2', 'asset4'); result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset2', 'asset6', '2', 'asset4');
console.log(`*** Result: ${prettyJSONString(result.toString())}`); console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('*** all tests completed'); console.log('*** all tests completed');

View file

@ -99,7 +99,7 @@ type Asset struct {
// HistoryQueryResult structure used for returning result of history query // HistoryQueryResult structure used for returning result of history query
type HistoryQueryResult struct { type HistoryQueryResult struct {
Record *Asset `json:"record"` Record *Asset `json:"record"`
TxId string `json:"txId"` TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
IsDelete bool `json:"isDelete"` IsDelete bool `json:"isDelete"`
} }
@ -331,15 +331,24 @@ func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, q
// The number of fetched records will be equal to or lesser than the page size. // The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions. // Paginated range queries are only valid for read only transactions.
// Example: Pagination with Range Query // Example: Pagination with Range Query
func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.TransactionContextInterface, startKey string, endKey string, pageSize int, bookmark string) ([]*Asset, error) { func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.TransactionContextInterface, startKey string, endKey string, pageSize int, bookmark string) (*PaginatedQueryResult, error) {
resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) resultsIterator, responseMetadata, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resultsIterator.Close() defer resultsIterator.Close()
return constructQueryResponseFromIterator(resultsIterator) assets, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}
return &PaginatedQueryResult{
Records: assets,
FetchedRecordsCount: responseMetadata.FetchedRecordsCount,
Bookmark: responseMetadata.Bookmark,
}, nil
} }
// QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query // QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query

View file

@ -266,10 +266,9 @@ class Chaincode extends Contract {
results.results = await this._GetAllResults(iterator, false); results.results = await this._GetAllResults(iterator, false);
results.ResponseMetadata = { results.fetchedRecordsCount = metadata.fetchedRecordsCount;
RecordsCount: metadata.fetchedRecordsCount,
Bookmark: metadata.bookmark, results.bookmark = metadata.bookmark;
};
return JSON.stringify(results); return JSON.stringify(results);
} }
@ -289,10 +288,9 @@ class Chaincode extends Contract {
results.results = await this._GetAllResults(iterator, false); results.results = await this._GetAllResults(iterator, false);
results.ResponseMetadata = { results.fetchedRecordsCount = metadata.fetchedRecordsCount;
RecordsCount: metadata.fetchedRecordsCount,
Bookmark: metadata.bookmark, results.bookmark = metadata.bookmark;
};
return JSON.stringify(results); return JSON.stringify(results);
} }

View file

@ -0,0 +1,78 @@
# Asset transfer private data sample
The asset transfer private data sample demonstrates:
- Usage of organization private data collections
- Read data from the organization private data collection.
- Store data in organization private data collection.
For more information about private data, visit the
[Private Data](https://hyperledger-fabric.readthedocs.io/en/latest/private-data-arch.html)
page in the Fabric documentation.
## About the sample
This sample includes smart contract and application code in multiple languages. In a use-case similar to basic asset transfer (see [asset-transfer-basic](../asset-transfer-basic) folder) this sample shows sending and receiving of asset along with its private data owned by organizations during create / delete of an asset , and during transfer of an asset to a new owner.
### Application
Please refer the below link to understand the application flow.
https://hyperledger-fabric.readthedocs.io/en/latest/private-data/private-data.html#example-scenario-asset-transfer-using-private-data-collections
### Smart Contract
The smart contract (in folder `chaincode-xyz`) implements the following functions to support the application:
CreateAsset
AgreeToTransfer
TransferAsset
DeleteAsset
DeleteTranferAgreement
ReadAsset
ReadAssetPrivateDetails
ReadTransferAgreement
GetAssetByRange
QueryAssetByOwner
QueryAssets
getQueryResultForQueryString
## Running the sample
Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
2. Deploy one of the smart contract implementations (from the `test-network` folder).
```
# To deploy the Java chaincode implementation
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-java -ccl java -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg '../asset-transfer-private-data/chaincode-java/collections_config.json' -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
# To deploy the go chaincode implementation
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-go -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg '../asset-transfer-private-data/chaincode-go/collections_config.json' -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
3. Run the application (from the `asset-transfer-private-data` folder).
```
# To run the Javascript sample application
cd application-javascript
npm install
node app.js
# To run the Typescript sample application
cd application-gateway-typescript
npm install
npm start
```
## Clean up
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down
```

View file

@ -0,0 +1,45 @@
{
"env": {
"node": true,
"es6": true
},
"root": true,
"ignorePatterns": [
"dist/"
],
"extends": [
"eslint:recommended"
],
"rules": {
"indent": [
"error",
4
],
"quotes": [
"error",
"single"
]
},
"overrides": [
{
"files": [
"**/*.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
}
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
]
}

View file

@ -0,0 +1,14 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

View file

@ -0,0 +1,10 @@
# Asset Transfer Private Data Sample
This app uses fabric-samples/test-network based setup and the companion chaincode asset-transfer-private-data/chaincode-go/ with chaincode endorsement policy as "OR('Org1MSP.peer','Org2MSP.peer')"
For this usecase illustration, we will use both Org1 & Org2 client identity from this same app
In real world the Org1 & Org2 identity will be used in different apps to achieve asset transfer.
For more details refer:
https://hyperledger-fabric.readthedocs.io/en/release-2.4/private_data_tutorial.html#pd-use-case

View file

@ -0,0 +1,32 @@
{
"name": "asset-transfer-private-data",
"version": "1.0.0",
"description": "Asset transfer private data application implemented in typeScript using fabric-gateway",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=14"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"lint": "eslint . --ext .ts",
"prepare": "npm run build",
"pretest": "npm run lint",
"start": "node dist/app.js"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.5.0",
"@hyperledger/fabric-gateway": "^1.0.0"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"eslint": "^8.4.1",
"typescript": "~4.5.2"
}
}

View file

@ -0,0 +1,286 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { connect, Contract } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import {
certPathOrg1, certPathOrg2, keyDirectoryPathOrg1, keyDirectoryPathOrg2, newGrpcConnection, newIdentity,
newSigner, peerEndpointOrg1, peerEndpointOrg2, peerNameOrg1, peerNameOrg2, tlsCertPathOrg1, tlsCertPathOrg2
} from './connect';
const channelName = 'mychannel';
const chaincodeName = 'private';
const mspIdOrg1 = 'Org1MSP';
const mspIdOrg2 = 'Org2MSP';
const utf8Decoder = new TextDecoder();
// Collection Names
const org1PrivateCollectionName = 'Org1MSPPrivateCollection';
const org2PrivateCollectionName = 'Org2MSPPrivateCollection';
const RED = '\x1b[31m\n';
const RESET = '\x1b[0m';
// Use a unique key so that we can run multiple times
const now = Date.now();
const assetID1 = `asset${now}`;
const assetID2 = `asset${now + 1}`;
async function main(): Promise<void> {
const clientOrg1 = await newGrpcConnection(
tlsCertPathOrg1,
peerEndpointOrg1,
peerNameOrg1
);
const gatewayOrg1 = connect({
client: clientOrg1,
identity: await newIdentity(certPathOrg1, mspIdOrg1),
signer: await newSigner(keyDirectoryPathOrg1),
});
const clientOrg2 = await newGrpcConnection(
tlsCertPathOrg2,
peerEndpointOrg2,
peerNameOrg2
);
const gatewayOrg2 = connect({
client: clientOrg2,
identity: await newIdentity(certPathOrg2, mspIdOrg2),
signer: await newSigner(keyDirectoryPathOrg2),
});
try {
// Get the smart contract as an Org1 client.
const contractOrg1 = gatewayOrg1
.getNetwork(channelName)
.getContract(chaincodeName);
// Get the smart contract as an Org2 client.
const contractOrg2 = gatewayOrg2
.getNetwork(channelName)
.getContract(chaincodeName);
console.log('\n~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~');
// Create new assets on the ledger.
await createAssets(contractOrg1);
// Read asset from the Org1's private data collection with ID in the given range.
await getAssetsByRange(contractOrg1);
try{
//Attempt to transfer asset without prior aprroval from Org2, transaction expected to fail.
console.log('\nAttempt TransferAsset without prior AgreeToTransfer');
await transferAsset(contractOrg1, assetID1);
doFail('TransferAsset transaction succeeded when it was expected to fail');
}
catch(e){
console.log(`*** Received expected error: ${e}`);
}
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
// Read the asset by ID.
await readAssetByID(contractOrg2, assetID1);
// Make agreement to transfer the asset from Org1 to Org2.
await agreeToTransfer(contractOrg2, assetID1);
console.log('\n~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~');
// Read transfer agreement.
await readTransferAgreement(contractOrg1, assetID1);
// Transfer asset to Org2.
await transferAsset(contractOrg1, assetID1);
// Again ReadAsset : results will show that the buyer identity now owns the asset.
await readAssetByID(contractOrg1, assetID1);
// Confirm that transfer removed the private details from the Org1 collection.
const org1ReadSuccess = await readAssetPrivateDetails(contractOrg1, assetID1, org1PrivateCollectionName);
if (org1ReadSuccess) {
doFail(`Asset private data still exists in ${org1PrivateCollectionName}`);
}
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
// Org2 can read asset private details: Org2 is owner, and private details exist in new owner's Collection
const org2ReadSuccess = await readAssetPrivateDetails(contractOrg2, assetID1, org2PrivateCollectionName);
if (!org2ReadSuccess) {
doFail(`Asset private data not found in ${org2PrivateCollectionName}`);
}
try {
console.log('\nAttempt DeleteAsset using non-owner organization');
await deleteAsset(contractOrg2, assetID2);
doFail('DeleteAsset transaction succeeded when it was expected to fail');
} catch (e) {
console.log(`*** Received expected error: ${e}`);
}
console.log('\n~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~');
// Delete AssetID2 as Org1.
await deleteAsset(contractOrg1, assetID2);
} finally {
gatewayOrg1.close();
clientOrg1.close();
gatewayOrg2.close();
clientOrg2.close();
}
}
main().catch((error) => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});
/**
* Submit a transaction synchronously, blocking until it has been committed to the ledger.
*/
async function createAssets(contract: Contract): Promise<void> {
const assetType = 'ValuableAsset';
console.log(`\n--> Submit Transaction: CreateAsset, ID: ${assetID1}`);
const asset1Data = {
objectType: assetType,
assetID: assetID1,
color: 'green',
size: 20,
appraisedValue: 100,
};
await contract.submit('CreateAsset', {
transientData: { asset_properties: JSON.stringify(asset1Data) },
});
console.log('*** Transaction committed successfully');
console.log(`\n--> Submit Transaction: CreateAsset, ID: ${assetID2}`);
const asset2Data = {
objectType: assetType,
assetID: assetID2,
color: 'blue',
size: 35,
appraisedValue: 727,
};
await contract.submit('CreateAsset', {
transientData: { asset_properties: JSON.stringify(asset2Data) },
});
console.log('*** Transaction committed successfully');
}
async function getAssetsByRange(contract: Contract): Promise<void> {
// GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive).
console.log(`\n--> Evaluate Transaction: ReadAssetPrivateDetails from ${org1PrivateCollectionName}`);
const resultBytes = await contract.evaluateTransaction(
'GetAssetByRange',
assetID1,
`asset${now + 2}`
);
const resultString = utf8Decoder.decode(resultBytes);
if (!resultString) {
doFail('Received empty query list for readAssetPrivateDetailsOrg1');
}
const result = JSON.parse(resultString);
console.log('*** Result:', result);
}
async function readAssetByID(contract: Contract, assetID: string): Promise<void> {
console.log(`\n--> Evaluate Transaction: ReadAsset, ID: ${assetID}`);
const resultBytes = await contract.evaluateTransaction('ReadAsset', assetID);
const resultString = utf8Decoder.decode(resultBytes);
if (!resultString) {
doFail('Received empty result for ReadAsset');
}
const result = JSON.parse(resultString);
console.log('*** Result:', result);
}
async function agreeToTransfer(contract: Contract, assetID: string): Promise<void> {
// Buyer from Org2 agrees to buy the asset//
// To purchase the asset, the buyer needs to agree to the same value as the asset owner
const dataForAgreement = { assetID, appraisedValue: 100 };
console.log('\n--> Submit Transaction: AgreeToTransfer, payload:', dataForAgreement);
await contract.submit('AgreeToTransfer', {
transientData: { asset_value: JSON.stringify(dataForAgreement) },
});
console.log('*** Transaction committed successfully');
}
async function readTransferAgreement(contract: Contract, assetID: string): Promise<void> {
console.log(`\n--> Evaluate Transaction: ReadTransferAgreement, ID: ${assetID}`);
const resultBytes = await contract.evaluateTransaction(
'ReadTransferAgreement',
assetID
);
const resultString = utf8Decoder.decode(resultBytes);
if (!resultString) {
doFail('Received no result for ReadTransferAgreement');
}
const result = JSON.parse(resultString);
console.log('*** Result:', result);
}
async function transferAsset(contract: Contract, assetID: string): Promise<void> {
console.log(`\n--> Submit Transaction: TransferAsset, ID: ${assetID}`);
const buyerDetails = { assetID, buyerMSP: mspIdOrg2 };
await contract.submit('TransferAsset', {
transientData: { asset_owner: JSON.stringify(buyerDetails) },
});
console.log('*** Transaction committed successfully');
}
async function deleteAsset(contract: Contract, assetID: string): Promise<void> {
console.log('\n--> Submit Transaction: DeleteAsset, ID:', assetID);
const dataForDelete = { assetID };
await contract.submit('DeleteAsset', {
transientData: { asset_delete: JSON.stringify(dataForDelete) },
});
console.log('*** Transaction committed successfully');
}
async function readAssetPrivateDetails(contract: Contract, assetID: string, collectionName: string): Promise<boolean> {
console.log(`\n--> Evaluate Transaction: ReadAssetPrivateDetails from ${collectionName}, ID: ${assetID}`);
const resultBytes = await contract.evaluateTransaction(
'ReadAssetPrivateDetails',
collectionName,
assetID
);
const resultJson = utf8Decoder.decode(resultBytes);
if (!resultJson) {
console.log('*** No result');
return false;
}
const result = JSON.parse(resultJson);
console.log('*** Result:', result);
return true;
}
export function doFail(msgString: string): never {
console.error(`${RED}\t${msgString}${RESET}`);
throw new Error(msgString);
}

View file

@ -0,0 +1,128 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
// Path to org1 crypto materials.
const cryptoPathOrg1 = path.resolve(
__dirname,
'..',
'..',
'..',
'test-network',
'organizations',
'peerOrganizations',
'org1.example.com'
);
// Path to org1 user private key directory.
export const keyDirectoryPathOrg1 = path.resolve(
cryptoPathOrg1,
'users',
'User1@org1.example.com',
'msp',
'keystore'
);
// Path to org1 user certificate.
export const certPathOrg1 = path.resolve(
cryptoPathOrg1,
'users',
'User1@org1.example.com',
'msp',
'signcerts',
'cert.pem'
);
// Path to org1 peer tls certificate.
export const tlsCertPathOrg1 = path.resolve(
cryptoPathOrg1,
'peers',
'peer0.org1.example.com',
'tls',
'ca.crt'
);
// Path to org2 crypto materials.
export const cryptoPathOrg2 = path.resolve(
__dirname,
'..',
'..',
'..',
'test-network',
'organizations',
'peerOrganizations',
'org2.example.com'
);
// Path to org2 user private key directory.
export const keyDirectoryPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'keystore'
);
// Path to org2 user certificate.
export const certPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'signcerts',
'cert.pem'
);
// Path to org2 peer tls certificate.
export const tlsCertPathOrg2 = path.resolve(
cryptoPathOrg2,
'peers',
'peer0.org2.example.com',
'tls',
'ca.crt'
);
// Gateway peer endpoint.
export const peerEndpointOrg1 = 'localhost:7051';
export const peerEndpointOrg2 = 'localhost:9051';
// Gateway peer container name.
export const peerNameOrg1 = 'peer0.org1.example.com';
export const peerNameOrg2 = 'peer0.org2.example.com';
export async function newGrpcConnection(
tlsCertPath: string,
peerEndpoint: string,
peerName: string
): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': peerName,
});
}
export async function newIdentity(
certPath: string,
mspId: string
): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(keyDirectoryPath: string): Promise<Signer> {
const files = await fs.readdir(keyDirectoryPath);
const keyPath = path.resolve(keyDirectoryPath, files[0]);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}

View file

@ -0,0 +1,18 @@
{
"extends":"@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"noImplicitAny": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
}

View file

@ -24,10 +24,7 @@ dependencies {
} }
repositories { repositories {
maven { mavenCentral()
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -27,10 +27,7 @@ dependencies {
} }
repositories { repositories {
maven { mavenCentral()
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -14,11 +14,7 @@ version '1.0-SNAPSHOT'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
maven {
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -22,10 +22,7 @@ dependencies {
} }
repositories { repositories {
maven { mavenCentral()
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

View file

@ -8,13 +8,26 @@ trigger:
- release-2.2 - release-2.2
variables: variables:
FABRIC_VERSION: 2.4 - name: FABRIC_VERSION
GO_BIN: $(Build.Repository.LocalPath)/bin value: 2.4
GO_VER: 1.16.7 - name: GO_BIN
NODE_VER: 16.x value: $(Build.Repository.LocalPath)/bin
PATH: $(Build.Repository.LocalPath)/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin - name: GO_VER
value: 1.16.7
- name: NODE_VER
value: 16.x
- name: PATH
value: $(Build.Repository.LocalPath)/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
- group: credentials
jobs: jobs:
- job: REST_Sample
displayName: REST Server Sample
pool:
vmImage: ubuntu-20.04
steps:
- template: templates/asset-transfer-basic/azure-pipelines-rest.yml
- job: CommercialPaper_Go - job: CommercialPaper_Go
displayName: Commercial Paper (Go) displayName: Commercial Paper (Go)
pool: pool:
@ -85,6 +98,8 @@ jobs:
inputs: inputs:
versionSpec: $(NODE_VER) versionSpec: $(NODE_VER)
displayName: Install Node.js displayName: Install Node.js
- script: ./ci/scripts/shellcheck.sh
displayName: Lint Shell Scripts
- script: ./ci/scripts/lint.sh - script: ./ci/scripts/lint.sh
displayName: Lint Code displayName: Lint Code
@ -114,6 +129,20 @@ jobs:
workingDirectory: test-network workingDirectory: test-network
displayName: Run Test Network Basic Chaincode displayName: Run Test Network Basic Chaincode
- job: KubeTestNetworkBasic
displayName: Kube Test Network Basic
pool:
vmImage: ubuntu-20.04
strategy:
matrix:
Docker-Typescript:
CLIENT_LANGUAGE: typescript
steps:
- template: templates/install-k8s-deps.yml
- script: ../ci/scripts/run-k8s-test-network-basic.sh
workingDirectory: test-network-k8s
displayName: Run Kubernetes Test Network Basic Asset Transfer
- job: TestNetworkLedger - job: TestNetworkLedger
displayName: Test Network displayName: Test Network
pool: pool:

View file

@ -0,0 +1,127 @@
#!/bin/bash -e
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
set -euo pipefail
# Test matrix parameters
export CONTAINER_CLI=${CONTAINER_CLI:-docker}
export CLIENT_LANGUAGE=${CLIENT_LANGUAGE:-typescript}
# Fabric version and Docker registry source: use the latest stable tag image from JFrog
export FABRIC_VERSION=${FABRIC_VERSION:-2.4}
export TEST_NETWORK_FABRIC_CONTAINER_REGISTRY=hyperledger-fabric.jfrog.io
export TEST_NETWORK_FABRIC_VERSION=amd64-${FABRIC_VERSION}-stable
export TEST_NETWORK_FABRIC_CA_VERSION=amd64-${FABRIC_VERSION}-stable
# test-network-k8s parameters
export TEST_TAG=$(git describe)
export TEST_NETWORK_KIND_CLUSTER_NAME=${TEST_NETWORK_KIND_CLUSTER_NAME:-kind}
export TEST_NETWORK_CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic}
export TEST_NETWORK_CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_NAME}:${TEST_TAG}
export TEST_NETWORK_CHAINCODE_PATH=${TEST_NETWORK_CHAINCODE_PATH:-../asset-transfer-basic/chaincode-external}
# gateway client application parameters
export GATEWAY_CLIENT_APPLICATION_PATH=${GATEWAY_CLIENT_APPLICATION_PATH:-../asset-transfer-basic/application-gateway-${CLIENT_LANGUAGE}}
export CHANNEL_NAME=${TEST_NETWORK_CHANNEL_NAME:-mychannel}
export CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic}
export MSP_ID=${MSP_ID:-Org1MSP}
export CRYPTO_PATH=${CRYPTO_PATH:-../../test-network-k8s/build/channel-msp/peerOrganizations/org1}
export KEY_DIRECTORY_PATH=${KEY_DIRECTORY_PATH:-../../test-network-k8s/build/enrollments/org1/users/org1admin/msp/keystore}
export CERT_PATH=${CERT_PATH:-../../test-network-k8s/build/enrollments/org1/users/org1admin/msp/signcerts/cert.pem}
export TLS_CERT_PATH=${TLS_CERT_PATH:-../../test-network-k8s/build/channel-msp/peerOrganizations/org1/msp/tlscacerts/tlsca-signcert.pem}
export PEER_ENDPOINT=${PEER_ENDPOINT:-org1-peer1.vcap.me:443}
export PEER_HOST_ALIAS=${PEER_HOST_ALIAS:-org1-peer1.vcap.me}
function print() {
GREEN='\033[0;32m'
NC='\033[0m'
echo
echo -e "${GREEN}${1}${NC}"
}
function touteSuite() {
createCluster
buildChaincodeImage
}
function quitterLaScene() {
destroyCluster
scrubCCImages
}
function createCluster() {
print "Initializing KIND Kubernetes cluster"
./network kind
}
function destroyCluster() {
print "Destroying KIND Kubernetes cluster"
./network unkind
}
function buildChaincodeImage() {
print "Building chaincode image $TEST_NETWORK_CHAINCODE_IMAGE"
${CONTAINER_CLI} build -t $TEST_NETWORK_CHAINCODE_IMAGE $TEST_NETWORK_CHAINCODE_PATH
# todo: work with local reg, or k3s, or KIND, or ...
kind load docker-image $TEST_NETWORK_CHAINCODE_IMAGE
}
function scrubCCImages() {
print "Scrubbing chaincode images"
${CONTAINER_CLI} rmi $TEST_NETWORK_CHAINCODE_IMAGE
}
function createNetwork() {
print "Launching network"
./network up
./network channel create
print "Deploying chaincode"
./network chaincode deploy
}
function stopNetwork() {
print "Stopping network"
./network down
}
# Set up the suite with a KIND cluster
touteSuite
trap "quitterLaScene" EXIT
# invoke / query
createNetwork
print "Inserting and querying assets"
( ./network chaincode invoke '{"Args":["InitLedger"]}' \
&& sleep 5 \
&& ./network chaincode query '{"Args":["ReadAsset","asset1"]}' )
print "OK"
print "Running rest-easy test"
( ./network rest-easy \
&& sleep 5 \
&& export SAMPLE_APIKEY='97834158-3224-4CE7-95F9-A148C886653E' \
&& curl -s --header "X-Api-Key: ${SAMPLE_APIKEY}" "http://fabric-rest-sample.vcap.me/api/assets/asset1" | jq )
print "OK"
stopNetwork
# Run the basic-asset-transfer basic application
createNetwork
print "Running Gateway client application"
( pushd ${GATEWAY_CLIENT_APPLICATION_PATH} \
&& npm install \
&& npm start )
print "OK"
stopNetwork
# Run additional test ...
# Run additional test ...
# Run additional test ...
# destroyCluster will be invoked on EXIT trap handler at the end of this suite.

View file

@ -12,8 +12,11 @@ function print() {
} }
function createNetwork() { function createNetwork() {
print "Creating network" print "Creating 3 Org network"
./network.sh up createChannel -ca -s couchdb ./network.sh up createChannel -ca -s couchdb
cd addOrg3
./addOrg3.sh up -ca -s couchdb
cd ..
print "Deploying ${CHAINCODE_NAME} chaincode" print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccp "${CHAINCODE_PATH}/chaincode-${CHAINCODE_LANGUAGE}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" ./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccp "${CHAINCODE_PATH}/chaincode-${CHAINCODE_LANGUAGE}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
} }
@ -41,6 +44,15 @@ gradle run
popd popd
stopNetwork stopNetwork
# Run Java application using gateway
createNetwork
print "Initializing Java application"
pushd ../asset-transfer-basic/application-gateway-java
print "Executing Gradle Run"
./gradlew run
popd
stopNetwork
# Run Javascript application # Run Javascript application
createNetwork createNetwork
print "Initializing Javascript application" print "Initializing Javascript application"

View file

@ -34,3 +34,34 @@ popd
stopNetwork stopNetwork
print "Remove wallet storage" print "Remove wallet storage"
rm -R ../asset-transfer-events/application-javascript/wallet rm -R ../asset-transfer-events/application-javascript/wallet
# Run typescript gateway application
createNetwork
print "Initializing TypeScript gateway application"
pushd ../asset-transfer-events/application-gateway-typescript
npm install
print "Build app"
npm run build
print "Executing dist/app.js"
npm start
popd
stopNetwork
# Run Go gateway application
createNetwork
print "Initializing Go gateway application"
pushd ../asset-transfer-events/application-gateway-go
print "Executing application"
go run .
popd
stopNetwork
# Run Java gateway application
createNetwork
print "Initializing Java gateway application"
pushd ../asset-transfer-events/application-gateway-java
print "Executing application"
./gradlew run
popd
stopNetwork

View file

@ -32,3 +32,16 @@ print "Executing app.js"
node app.js node app.js
popd popd
stopNetwork stopNetwork
# Run typescript gateway application
createNetwork
print "Initializing typescript application"
pushd ../asset-transfer-private-data/application-gateway-typescript
npm install
print "Build typescript app"
npm run build
print "Executing app.js"
npm start
popd
stopNetwork

8
ci/scripts/shellcheck.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
scversion="v0.8.0" # or "stable", or "latest"
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv "shellcheck-${scversion}/shellcheck"
"./shellcheck-${scversion}/shellcheck" --version
"./shellcheck-${scversion}/shellcheck" ./test-network-nano-bash/*.sh

View file

@ -0,0 +1,31 @@
#
# SPDX-License-Identifier: Apache-2.0
#
steps:
- task: NodeTool@0
inputs:
versionSpec: $(NODE_VER)
displayName: Install Node.js
- script: npm install
workingDirectory: asset-transfer-basic/rest-api-typescript
displayName: Install REST Sample Dependencies
- script: npm run build
workingDirectory: asset-transfer-basic/rest-api-typescript
displayName: Build REST Sample Application
- script: npm test
workingDirectory: asset-transfer-basic/rest-api-typescript
displayName: Test REST Sample Application
- script: |
docker build -t ghcr.io/hyperledger/fabric-rest-sample .
workingDirectory: asset-transfer-basic/rest-api-typescript
displayName: Build REST Sample Docker Image
- script: |
echo ${GITHUB_PAT} | docker login ghcr.io -u ${GITHUB_USER} --password-stdin
docker push ghcr.io/hyperledger/fabric-rest-sample:latest
condition: and(succeeded(),eq(variables['Build.Reason'], 'IndividualCI'))
workingDirectory: asset-transfer-basic/rest-api-typescript
displayName: Publish REST Sample Docker Image
env:
GITHUB_USER: $(GITHUB_USER)
GITHUB_PAT: $(GITHUB_PAT)

View file

@ -0,0 +1,9 @@
#
# SPDX-License-Identifier: Apache-2.0
#
steps:
- task: NodeTool@0
inputs:
versionSpec: $(NODE_VER)
displayName: Install Node.js

View file

@ -7,7 +7,6 @@ version '0.0.1'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'

View file

@ -12,7 +12,6 @@ version '0.0.1'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'

View file

@ -12,7 +12,6 @@ version '0.0.1'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'

View file

@ -3,3 +3,4 @@ network.log
network-debug.log network-debug.log
build/ build/
.env .env
bin/

View file

@ -42,7 +42,7 @@ Invoke and query chaincode:
./network chaincode query '{"Args":["ReadAsset","1"]}' ./network chaincode query '{"Args":["ReadAsset","1"]}'
``` ```
Access the blockchain with a [REST API](https://github.com/hyperledgendary/fabric-rest-sample/tree/main/asset-transfer-basic/rest-api-typescript#rest-api): Access the blockchain with a [REST API](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/rest-api-typescript):
``` ```
./network rest-easy ./network rest-easy
``` ```
@ -71,8 +71,8 @@ Tear down the cluster:
## Areas for Improvement / TODOs ## Areas for Improvement / TODOs
- [ ] Refine the recipe and guidelines for use with `k3s` / `nerdctl` (rancherdesktop.io) as an alternative to Docker / KIND.
- [ ] Test the recipe with OCP, AWS, gcp, Azure, etc. (These should ONLY differ w.r.t. pvc and ingress) - [ ] Test the recipe with OCP, AWS, gcp, Azure, etc. (These should ONLY differ w.r.t. pvc and ingress)
- [ ] Implement @celder mechanism for bootstrapping dual-headed CAs w/o poisoning the root CA on expiry.
- [ ] Address any of the 20+ todo: notes in network.sh - [ ] Address any of the 20+ todo: notes in network.sh
- [ ] Implement mutual TLS across peers, orderers, and clients. - [ ] Implement mutual TLS across peers, orderers, and clients.
- [ ] Caliper? - [ ] Caliper?

View file

@ -25,7 +25,7 @@ Organizations:
ID: OrdererMSP ID: OrdererMSP
# MSPDir is the filesystem path which contains the MSP configuration # MSPDir is the filesystem path which contains the MSP configuration
MSPDir: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp MSPDir: ../../build/channel-msp/ordererOrganizations/org0/msp
# Policies defines the set of policies at this level of the config tree # Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually # For organization policies, their canonical path is usually
@ -54,7 +54,7 @@ Organizations:
# ID to load the MSP definition as # ID to load the MSP definition as
ID: Org1MSP ID: Org1MSP
MSPDir: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/msp MSPDir: ../../build/channel-msp/peerOrganizations/org1/msp
# Policies defines the set of policies at this level of the config tree # Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually # For organization policies, their canonical path is usually
@ -89,7 +89,7 @@ Organizations:
# ID to load the MSP definition as # ID to load the MSP definition as
ID: Org2MSP ID: Org2MSP
MSPDir: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/msp MSPDir: ../../build/channel-msp/peerOrganizations/org2/msp
# Policies defines the set of policies at this level of the config tree # Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually # For organization policies, their canonical path is usually
@ -224,16 +224,16 @@ Orderer: &OrdererDefaults
Consenters: Consenters:
- Host: org0-orderer1 - Host: org0-orderer1
Port: 6050 Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/signcerts/cert.pem ClientTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem
ServerTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/signcerts/cert.pem ServerTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem
- Host: org0-orderer2 - Host: org0-orderer2
Port: 6050 Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/signcerts/cert.pem ClientTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer2/tls/signcerts/tls-cert.pem
ServerTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/signcerts/cert.pem ServerTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer2/tls/signcerts/tls-cert.pem
- Host: org0-orderer3 - Host: org0-orderer3
Port: 6050 Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/signcerts/cert.pem ClientTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer3/tls/signcerts/tls-cert.pem
ServerTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/signcerts/cert.pem ServerTLSCert: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer3/tls/signcerts/tls-cert.pem
# Options to be specified for all the etcd/raft nodes. The values here # Options to be specified for all the etcd/raft nodes. The values here

View file

@ -86,7 +86,7 @@ tls:
############################################################################# #############################################################################
ca: ca:
# Name of this CA # Name of this CA
name: org0-ecert-ca name: org0-ca
# Key file (is only used to import a private key into BCCSP) # Key file (is only used to import a private key into BCCSP)
keyfile: keyfile:
# Certificate file (default: ca-cert.pem) # Certificate file (default: ca-cert.pem)
@ -320,8 +320,8 @@ csr:
hosts: hosts:
- localhost - localhost
- 127.0.0.1 - 127.0.0.1
- org0-ecert-ca - org0-ca
- org0-ecert-ca.test-network.svc.cluster.local - org0-ca.test-network.svc.cluster.local
ca: ca:
expiry: 131400h expiry: 131400h
pathlength: 1 pathlength: 1

View file

@ -1,496 +0,0 @@
#############################################################################
# This is a configuration file for the fabric-ca-server command.
#
# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
# ------------------------------------------------
# Each configuration element can be overridden via command line
# arguments or environment variables. The precedence for determining
# the value of each element is as follows:
# 1) command line argument
# Examples:
# a) --port 443
# To set the listening port
# b) --ca.keyfile ../mykey.pem
# To set the "keyfile" element in the "ca" section below;
# note the '.' separator character.
# 2) environment variable
# Examples:
# a) FABRIC_CA_SERVER_PORT=443
# To set the listening port
# b) FABRIC_CA_SERVER_CA_KEYFILE="../mykey.pem"
# To set the "keyfile" element in the "ca" section below;
# note the '_' separator character.
# 3) configuration file
# 4) default value (if there is one)
# All default values are shown beside each element below.
#
# FILE NAME ELEMENTS
# ------------------
# The value of all fields whose name ends with "file" or "files" are
# name or names of other files.
# For example, see "tls.certfile" and "tls.clientauth.certfiles".
# The value of each of these fields can be a simple filename, a
# relative path, or an absolute path. If the value is not an
# absolute path, it is interpretted as being relative to the location
# of this configuration file.
#
#############################################################################
# Version of config file
version: 1.5.2
# Server's listening port (default: 7054)
port: 443
# Cross-Origin Resource Sharing (CORS)
cors:
enabled: false
origins:
- "*"
# Enables debug logging (default: false)
debug: false
# Size limit of an acceptable CRL in bytes (default: 512000)
crlsizelimit: 512000
#############################################################################
# TLS section for the server's listening port
#
# The following types are supported for client authentication: NoClientCert,
# RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven,
# and RequireAndVerifyClientCert.
#
# Certfiles is a list of root certificate authorities that the server uses
# when verifying client certificates.
#############################################################################
tls:
# Enable TLS (default: false)
enabled: true
# TLS for the server's listening port
certfile:
keyfile:
clientauth:
type: noclientcert
certfiles:
#############################################################################
# The CA section contains information related to the Certificate Authority
# including the name of the CA, which should be unique for all members
# of a blockchain network. It also includes the key and certificate files
# used when issuing enrollment certificates (ECerts) and transaction
# certificates (TCerts).
# The chainfile (if it exists) contains the certificate chain which
# should be trusted for this CA, where the 1st in the chain is always the
# root CA certificate.
#############################################################################
ca:
# Name of this CA
name: org0-tls-ca
# Key file (is only used to import a private key into BCCSP)
keyfile:
# Certificate file (default: ca-cert.pem)
certfile:
# Chain file
chainfile:
#############################################################################
# The gencrl REST endpoint is used to generate a CRL that contains revoked
# certificates. This section contains configuration options that are used
# during gencrl request processing.
#############################################################################
crl:
# Specifies expiration for the generated CRL. The number of hours
# specified by this property is added to the UTC time, the resulting time
# is used to set the 'Next Update' date of the CRL.
expiry: 24h
#############################################################################
# The registry section controls how the fabric-ca-server does two things:
# 1) authenticates enrollment requests which contain a username and password
# (also known as an enrollment ID and secret).
# 2) once authenticated, retrieves the identity's attribute names and
# values which the fabric-ca-server optionally puts into TCerts
# which it issues for transacting on the Hyperledger Fabric blockchain.
# These attributes are useful for making access control decisions in
# chaincode.
# There are two main configuration options:
# 1) The fabric-ca-server is the registry.
# This is true if "ldap.enabled" in the ldap section below is false.
# 2) An LDAP server is the registry, in which case the fabric-ca-server
# calls the LDAP server to perform these tasks.
# This is true if "ldap.enabled" in the ldap section below is true,
# which means this "registry" section is ignored.
#############################################################################
registry:
# Maximum number of times a password/secret can be reused for enrollment
# (default: -1, which means there is no limit)
maxenrollments: -1
# Contains identity information which is used when LDAP is disabled
identities:
- name: tlsadmin
pass: tlsadminpw
type: client
affiliation: ""
attrs:
hf.Registrar.Roles: "*"
hf.Registrar.DelegateRoles: "*"
hf.Revoker: true
hf.IntermediateCA: true
hf.GenCRL: true
hf.Registrar.Attributes: "*"
hf.AffiliationMgr: true
#############################################################################
# Database section
# Supported types are: "sqlite3", "postgres", and "mysql".
# The datasource value depends on the type.
# If the type is "sqlite3", the datasource value is a file name to use
# as the database store. Since "sqlite3" is an embedded database, it
# may not be used if you want to run the fabric-ca-server in a cluster.
# To run the fabric-ca-server in a cluster, you must choose "postgres"
# or "mysql".
#############################################################################
db:
type: sqlite3
datasource: fabric-ca-server.db
tls:
enabled: false
certfiles:
client:
certfile:
keyfile:
#############################################################################
# LDAP section
# If LDAP is enabled, the fabric-ca-server calls LDAP to:
# 1) authenticate enrollment ID and secret (i.e. username and password)
# for enrollment requests;
# 2) To retrieve identity attributes
#############################################################################
ldap:
# Enables or disables the LDAP client (default: false)
# If this is set to true, the "registry" section is ignored.
enabled: false
# The URL of the LDAP server
url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
# TLS configuration for the client connection to the LDAP server
tls:
certfiles:
client:
certfile:
keyfile:
# Attribute related configuration for mapping from LDAP entries to Fabric CA attributes
attribute:
# 'names' is an array of strings containing the LDAP attribute names which are
# requested from the LDAP server for an LDAP identity's entry
names: ['uid','member']
# The 'converters' section is used to convert an LDAP entry to the value of
# a fabric CA attribute.
# For example, the following converts an LDAP 'uid' attribute
# whose value begins with 'revoker' to a fabric CA attribute
# named "hf.Revoker" with a value of "true" (because the boolean expression
# evaluates to true).
# converters:
# - name: hf.Revoker
# value: attr("uid") =~ "revoker*"
converters:
- name:
value:
# The 'maps' section contains named maps which may be referenced by the 'map'
# function in the 'converters' section to map LDAP responses to arbitrary values.
# For example, assume a user has an LDAP attribute named 'member' which has multiple
# values which are each a distinguished name (i.e. a DN). For simplicity, assume the
# values of the 'member' attribute are 'dn1', 'dn2', and 'dn3'.
# Further assume the following configuration.
# converters:
# - name: hf.Registrar.Roles
# value: map(attr("member"),"groups")
# maps:
# groups:
# - name: dn1
# value: peer
# - name: dn2
# value: client
# The value of the user's 'hf.Registrar.Roles' attribute is then computed to be
# "peer,client,dn3". This is because the value of 'attr("member")' is
# "dn1,dn2,dn3", and the call to 'map' with a 2nd argument of
# "group" replaces "dn1" with "peer" and "dn2" with "client".
maps:
groups:
- name:
value:
#############################################################################
# Affiliations section. Fabric CA server can be bootstrapped with the
# affiliations specified in this section. Affiliations are specified as maps.
# For example:
# businessunit1:
# department1:
# - team1
# businessunit2:
# - department2
# - department3
#
# Affiliations are hierarchical in nature. In the above example,
# department1 (used as businessunit1.department1) is the child of businessunit1.
# team1 (used as businessunit1.department1.team1) is the child of department1.
# department2 (used as businessunit2.department2) and department3 (businessunit2.department3)
# are children of businessunit2.
# Note: Affiliations are case sensitive except for the non-leaf affiliations
# (like businessunit1, department1, businessunit2) that are specified in the configuration file,
# which are always stored in lower case.
#############################################################################
affiliations:
org1:
- department1
- department2
org2:
- department1
#############################################################################
# Signing section
#
# The "default" subsection is used to sign enrollment certificates;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#
# The "ca" profile subsection is used to sign intermediate CA certificates;
# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
# Note that "isca" is true, meaning that it issues a CA certificate.
# A maxpathlen of 0 means that the intermediate CA cannot issue other
# intermediate CA certificates, though it can still issue end entity certificates.
# (See RFC 5280, section 4.2.1.9)
#
# The "tls" profile subsection is used to sign TLS certificate requests;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#############################################################################
signing:
default:
authremote: {}
caconstraint: {}
expiry: 8760h
usage:
- signing
- key encipherment
- server auth
- client auth
- key agreement
profiles: null
###########################################################################
# Certificate Signing Request (CSR) section.
# This controls the creation of the root CA certificate.
# The expiration for the root CA certificate is configured with the
# "ca.expiry" field below, whose default value is "131400h" which is
# 15 years in hours.
# The pathlength field is used to limit CA certificate hierarchy as described
# in section 4.2.1.9 of RFC 5280.
# Examples:
# 1) No pathlength value means no limit is requested.
# 2) pathlength == 1 means a limit of 1 is requested which is the default for
# a root CA. This means the root CA can issue intermediate CA certificates,
# but these intermediate CAs may not in turn issue other CA certificates
# though they can still issue end entity certificates.
# 3) pathlength == 0 means a limit of 0 is requested;
# this is the default for an intermediate CA, which means it can not issue
# CA certificates though it can still issue end entity certificates.
###########################################################################
csr:
cn: fabric-ca-server
keyrequest:
algo: ecdsa
size: 256
names:
- C: US
ST: "North Carolina"
L:
O: Hyperledger
OU: Fabric
hosts:
- localhost
- 127.0.0.1
- org0-tls-ca
- org0-tls-ca.test-network.svc.cluster.local
ca:
expiry: 131400h
pathlength: 1
###########################################################################
# Each CA can issue both X509 enrollment certificate as well as Idemix
# Credential. This section specifies configuration for the issuer component
# that is responsible for issuing Idemix credentials.
###########################################################################
idemix:
# Specifies pool size for revocation handles. A revocation handle is an unique identifier of an
# Idemix credential. The issuer will create a pool revocation handles of this specified size. When
# a credential is requested, issuer will get handle from the pool and assign it to the credential.
# Issuer will repopulate the pool with new handles when the last handle in the pool is used.
# A revocation handle and credential revocation information (CRI) are used to create non revocation proof
# by the prover to prove to the verifier that her credential is not revoked.
rhpoolsize: 1000
# The Idemix credential issuance is a two step process. First step is to get a nonce from the issuer
# and second step is send credential request that is constructed using the nonce to the isuser to
# request a credential. This configuration property specifies expiration for the nonces. By default is
# nonces expire after 15 seconds. The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration).
nonceexpiration: 15s
# Specifies interval at which expired nonces are removed from datastore. Default value is 15 minutes.
# The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration)
noncesweepinterval: 15m
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section is used to select which
# crypto library implementation to use
#############################################################################
bccsp:
default: SW
sw:
hash: SHA2
security: 256
filekeystore:
# The directory used for the software file-based keystore
keystore: msp/keystore
#############################################################################
# Multi CA section
#
# Each Fabric CA server contains one CA by default. This section is used
# to configure multiple CAs in a single server.
#
# 1) --cacount <number-of-CAs>
# Automatically generate <number-of-CAs> non-default CAs. The names of these
# additional CAs are "ca1", "ca2", ... "caN", where "N" is <number-of-CAs>
# This is particularly useful in a development environment to quickly set up
# multiple CAs. Note that, this config option is not applicable to intermediate CA server
# i.e., Fabric CA server that is started with intermediate.parentserver.url config
# option (-u command line option)
#
# 2) --cafiles <CA-config-files>
# For each CA config file in the list, generate a separate signing CA. Each CA
# config file in this list MAY contain all of the same elements as are found in
# the server config file except port, debug, and tls sections.
#
# Examples:
# fabric-ca-server start -b admin:adminpw --cacount 2
#
# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
# --cafiles ca/ca2/fabric-ca-server-config.yaml
#
#############################################################################
cacount:
cafiles:
#############################################################################
# Intermediate CA section
#
# The relationship between servers and CAs is as follows:
# 1) A single server process may contain or function as one or more CAs.
# This is configured by the "Multi CA section" above.
# 2) Each CA is either a root CA or an intermediate CA.
# 3) Each intermediate CA has a parent CA which is either a root CA or another intermediate CA.
#
# This section pertains to configuration of #2 and #3.
# If the "intermediate.parentserver.url" property is set,
# then this is an intermediate CA with the specified parent
# CA.
#
# parentserver section
# url - The URL of the parent server
# caname - Name of the CA to enroll within the server
#
# enrollment section used to enroll intermediate CA with parent CA
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#
# tls section for secure socket connection
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
intermediate:
parentserver:
url:
caname:
enrollment:
hosts:
profile:
label:
tls:
certfiles:
client:
certfile:
keyfile:
#############################################################################
# CA configuration section
#
# Configure the number of incorrect password attempts are allowed for
# identities. By default, the value of 'passwordattempts' is 10, which
# means that 10 incorrect password attempts can be made before an identity get
# locked out.
#############################################################################
cfg:
identities:
passwordattempts: 10
###############################################################################
#
# Operations section
#
###############################################################################
operations:
# host and port for the operations server
listenAddress: 127.0.0.1:9444
# TLS configuration for the operations endpoint
tls:
# TLS enabled
enabled: false
# path to PEM encoded server certificate for the operations server
cert:
file:
# path to PEM encoded server key for the operations server
key:
file:
# require client certificate authentication to access all resources
clientAuthRequired: false
# paths to PEM encoded ca certificates to trust for client authentication
clientRootCAs:
files: []
###############################################################################
#
# Metrics section
#
###############################################################################
metrics:
# statsd, prometheus, or disabled
provider: disabled
# statsd configuration
statsd:
# network type: tcp or udp
network: udp
# statsd server address
address: 127.0.0.1:8125
# the interval at which locally cached counters and gauges are pushsed
# to statsd; timings are pushed immediately
writeInterval: 10s
# prefix is prepended to all emitted statsd merics
prefix: server

View file

@ -86,7 +86,7 @@ tls:
############################################################################# #############################################################################
ca: ca:
# Name of this CA # Name of this CA
name: org2-ecert-ca name: org1-ca
# Key file (is only used to import a private key into BCCSP) # Key file (is only used to import a private key into BCCSP)
keyfile: keyfile:
# Certificate file (default: ca-cert.pem) # Certificate file (default: ca-cert.pem)
@ -320,8 +320,8 @@ csr:
hosts: hosts:
- localhost - localhost
- 127.0.0.1 - 127.0.0.1
- org2-ecert-ca - org1-ca
- org2-ecert-ca.test-network.svc.cluster.local - org1-ca.test-network.svc.cluster.local
ca: ca:
expiry: 131400h expiry: 131400h
pathlength: 1 pathlength: 1

View file

@ -1,496 +0,0 @@
#############################################################################
# This is a configuration file for the fabric-ca-server command.
#
# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
# ------------------------------------------------
# Each configuration element can be overridden via command line
# arguments or environment variables. The precedence for determining
# the value of each element is as follows:
# 1) command line argument
# Examples:
# a) --port 443
# To set the listening port
# b) --ca.keyfile ../mykey.pem
# To set the "keyfile" element in the "ca" section below;
# note the '.' separator character.
# 2) environment variable
# Examples:
# a) FABRIC_CA_SERVER_PORT=443
# To set the listening port
# b) FABRIC_CA_SERVER_CA_KEYFILE="../mykey.pem"
# To set the "keyfile" element in the "ca" section below;
# note the '_' separator character.
# 3) configuration file
# 4) default value (if there is one)
# All default values are shown beside each element below.
#
# FILE NAME ELEMENTS
# ------------------
# The value of all fields whose name ends with "file" or "files" are
# name or names of other files.
# For example, see "tls.certfile" and "tls.clientauth.certfiles".
# The value of each of these fields can be a simple filename, a
# relative path, or an absolute path. If the value is not an
# absolute path, it is interpretted as being relative to the location
# of this configuration file.
#
#############################################################################
# Version of config file
version: 1.5.2
# Server's listening port (default: 7054)
port: 443
# Cross-Origin Resource Sharing (CORS)
cors:
enabled: false
origins:
- "*"
# Enables debug logging (default: false)
debug: false
# Size limit of an acceptable CRL in bytes (default: 512000)
crlsizelimit: 512000
#############################################################################
# TLS section for the server's listening port
#
# The following types are supported for client authentication: NoClientCert,
# RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven,
# and RequireAndVerifyClientCert.
#
# Certfiles is a list of root certificate authorities that the server uses
# when verifying client certificates.
#############################################################################
tls:
# Enable TLS (default: false)
enabled: true
# TLS for the server's listening port
certfile:
keyfile:
clientauth:
type: noclientcert
certfiles:
#############################################################################
# The CA section contains information related to the Certificate Authority
# including the name of the CA, which should be unique for all members
# of a blockchain network. It also includes the key and certificate files
# used when issuing enrollment certificates (ECerts) and transaction
# certificates (TCerts).
# The chainfile (if it exists) contains the certificate chain which
# should be trusted for this CA, where the 1st in the chain is always the
# root CA certificate.
#############################################################################
ca:
# Name of this CA
name: org1-tls-ca
# Key file (is only used to import a private key into BCCSP)
keyfile:
# Certificate file (default: ca-cert.pem)
certfile:
# Chain file
chainfile:
#############################################################################
# The gencrl REST endpoint is used to generate a CRL that contains revoked
# certificates. This section contains configuration options that are used
# during gencrl request processing.
#############################################################################
crl:
# Specifies expiration for the generated CRL. The number of hours
# specified by this property is added to the UTC time, the resulting time
# is used to set the 'Next Update' date of the CRL.
expiry: 24h
#############################################################################
# The registry section controls how the fabric-ca-server does two things:
# 1) authenticates enrollment requests which contain a username and password
# (also known as an enrollment ID and secret).
# 2) once authenticated, retrieves the identity's attribute names and
# values which the fabric-ca-server optionally puts into TCerts
# which it issues for transacting on the Hyperledger Fabric blockchain.
# These attributes are useful for making access control decisions in
# chaincode.
# There are two main configuration options:
# 1) The fabric-ca-server is the registry.
# This is true if "ldap.enabled" in the ldap section below is false.
# 2) An LDAP server is the registry, in which case the fabric-ca-server
# calls the LDAP server to perform these tasks.
# This is true if "ldap.enabled" in the ldap section below is true,
# which means this "registry" section is ignored.
#############################################################################
registry:
# Maximum number of times a password/secret can be reused for enrollment
# (default: -1, which means there is no limit)
maxenrollments: -1
# Contains identity information which is used when LDAP is disabled
identities:
- name: tlsadmin
pass: tlsadminpw
type: client
affiliation: ""
attrs:
hf.Registrar.Roles: "*"
hf.Registrar.DelegateRoles: "*"
hf.Revoker: true
hf.IntermediateCA: true
hf.GenCRL: true
hf.Registrar.Attributes: "*"
hf.AffiliationMgr: true
#############################################################################
# Database section
# Supported types are: "sqlite3", "postgres", and "mysql".
# The datasource value depends on the type.
# If the type is "sqlite3", the datasource value is a file name to use
# as the database store. Since "sqlite3" is an embedded database, it
# may not be used if you want to run the fabric-ca-server in a cluster.
# To run the fabric-ca-server in a cluster, you must choose "postgres"
# or "mysql".
#############################################################################
db:
type: sqlite3
datasource: fabric-ca-server.db
tls:
enabled: false
certfiles:
client:
certfile:
keyfile:
#############################################################################
# LDAP section
# If LDAP is enabled, the fabric-ca-server calls LDAP to:
# 1) authenticate enrollment ID and secret (i.e. username and password)
# for enrollment requests;
# 2) To retrieve identity attributes
#############################################################################
ldap:
# Enables or disables the LDAP client (default: false)
# If this is set to true, the "registry" section is ignored.
enabled: false
# The URL of the LDAP server
url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
# TLS configuration for the client connection to the LDAP server
tls:
certfiles:
client:
certfile:
keyfile:
# Attribute related configuration for mapping from LDAP entries to Fabric CA attributes
attribute:
# 'names' is an array of strings containing the LDAP attribute names which are
# requested from the LDAP server for an LDAP identity's entry
names: ['uid','member']
# The 'converters' section is used to convert an LDAP entry to the value of
# a fabric CA attribute.
# For example, the following converts an LDAP 'uid' attribute
# whose value begins with 'revoker' to a fabric CA attribute
# named "hf.Revoker" with a value of "true" (because the boolean expression
# evaluates to true).
# converters:
# - name: hf.Revoker
# value: attr("uid") =~ "revoker*"
converters:
- name:
value:
# The 'maps' section contains named maps which may be referenced by the 'map'
# function in the 'converters' section to map LDAP responses to arbitrary values.
# For example, assume a user has an LDAP attribute named 'member' which has multiple
# values which are each a distinguished name (i.e. a DN). For simplicity, assume the
# values of the 'member' attribute are 'dn1', 'dn2', and 'dn3'.
# Further assume the following configuration.
# converters:
# - name: hf.Registrar.Roles
# value: map(attr("member"),"groups")
# maps:
# groups:
# - name: dn1
# value: peer
# - name: dn2
# value: client
# The value of the user's 'hf.Registrar.Roles' attribute is then computed to be
# "peer,client,dn3". This is because the value of 'attr("member")' is
# "dn1,dn2,dn3", and the call to 'map' with a 2nd argument of
# "group" replaces "dn1" with "peer" and "dn2" with "client".
maps:
groups:
- name:
value:
#############################################################################
# Affiliations section. Fabric CA server can be bootstrapped with the
# affiliations specified in this section. Affiliations are specified as maps.
# For example:
# businessunit1:
# department1:
# - team1
# businessunit2:
# - department2
# - department3
#
# Affiliations are hierarchical in nature. In the above example,
# department1 (used as businessunit1.department1) is the child of businessunit1.
# team1 (used as businessunit1.department1.team1) is the child of department1.
# department2 (used as businessunit2.department2) and department3 (businessunit2.department3)
# are children of businessunit2.
# Note: Affiliations are case sensitive except for the non-leaf affiliations
# (like businessunit1, department1, businessunit2) that are specified in the configuration file,
# which are always stored in lower case.
#############################################################################
affiliations:
org1:
- department1
- department2
org2:
- department1
#############################################################################
# Signing section
#
# The "default" subsection is used to sign enrollment certificates;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#
# The "ca" profile subsection is used to sign intermediate CA certificates;
# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
# Note that "isca" is true, meaning that it issues a CA certificate.
# A maxpathlen of 0 means that the intermediate CA cannot issue other
# intermediate CA certificates, though it can still issue end entity certificates.
# (See RFC 5280, section 4.2.1.9)
#
# The "tls" profile subsection is used to sign TLS certificate requests;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#############################################################################
signing:
default:
authremote: {}
caconstraint: {}
expiry: 8760h
usage:
- signing
- key encipherment
- server auth
- client auth
- key agreement
profiles: null
###########################################################################
# Certificate Signing Request (CSR) section.
# This controls the creation of the root CA certificate.
# The expiration for the root CA certificate is configured with the
# "ca.expiry" field below, whose default value is "131400h" which is
# 15 years in hours.
# The pathlength field is used to limit CA certificate hierarchy as described
# in section 4.2.1.9 of RFC 5280.
# Examples:
# 1) No pathlength value means no limit is requested.
# 2) pathlength == 1 means a limit of 1 is requested which is the default for
# a root CA. This means the root CA can issue intermediate CA certificates,
# but these intermediate CAs may not in turn issue other CA certificates
# though they can still issue end entity certificates.
# 3) pathlength == 0 means a limit of 0 is requested;
# this is the default for an intermediate CA, which means it can not issue
# CA certificates though it can still issue end entity certificates.
###########################################################################
csr:
cn: fabric-ca-server
keyrequest:
algo: ecdsa
size: 256
names:
- C: US
ST: "North Carolina"
L:
O: Hyperledger
OU: Fabric
hosts:
- localhost
- 127.0.0.1
- org1-tls-ca
- org1-tls-ca.test-network.svc.cluster.local
ca:
expiry: 131400h
pathlength: 1
###########################################################################
# Each CA can issue both X509 enrollment certificate as well as Idemix
# Credential. This section specifies configuration for the issuer component
# that is responsible for issuing Idemix credentials.
###########################################################################
idemix:
# Specifies pool size for revocation handles. A revocation handle is an unique identifier of an
# Idemix credential. The issuer will create a pool revocation handles of this specified size. When
# a credential is requested, issuer will get handle from the pool and assign it to the credential.
# Issuer will repopulate the pool with new handles when the last handle in the pool is used.
# A revocation handle and credential revocation information (CRI) are used to create non revocation proof
# by the prover to prove to the verifier that her credential is not revoked.
rhpoolsize: 1000
# The Idemix credential issuance is a two step process. First step is to get a nonce from the issuer
# and second step is send credential request that is constructed using the nonce to the isuser to
# request a credential. This configuration property specifies expiration for the nonces. By default is
# nonces expire after 15 seconds. The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration).
nonceexpiration: 15s
# Specifies interval at which expired nonces are removed from datastore. Default value is 15 minutes.
# The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration)
noncesweepinterval: 15m
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section is used to select which
# crypto library implementation to use
#############################################################################
bccsp:
default: SW
sw:
hash: SHA2
security: 256
filekeystore:
# The directory used for the software file-based keystore
keystore: msp/keystore
#############################################################################
# Multi CA section
#
# Each Fabric CA server contains one CA by default. This section is used
# to configure multiple CAs in a single server.
#
# 1) --cacount <number-of-CAs>
# Automatically generate <number-of-CAs> non-default CAs. The names of these
# additional CAs are "ca1", "ca2", ... "caN", where "N" is <number-of-CAs>
# This is particularly useful in a development environment to quickly set up
# multiple CAs. Note that, this config option is not applicable to intermediate CA server
# i.e., Fabric CA server that is started with intermediate.parentserver.url config
# option (-u command line option)
#
# 2) --cafiles <CA-config-files>
# For each CA config file in the list, generate a separate signing CA. Each CA
# config file in this list MAY contain all of the same elements as are found in
# the server config file except port, debug, and tls sections.
#
# Examples:
# fabric-ca-server start -b admin:adminpw --cacount 2
#
# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
# --cafiles ca/ca2/fabric-ca-server-config.yaml
#
#############################################################################
cacount:
cafiles:
#############################################################################
# Intermediate CA section
#
# The relationship between servers and CAs is as follows:
# 1) A single server process may contain or function as one or more CAs.
# This is configured by the "Multi CA section" above.
# 2) Each CA is either a root CA or an intermediate CA.
# 3) Each intermediate CA has a parent CA which is either a root CA or another intermediate CA.
#
# This section pertains to configuration of #2 and #3.
# If the "intermediate.parentserver.url" property is set,
# then this is an intermediate CA with the specified parent
# CA.
#
# parentserver section
# url - The URL of the parent server
# caname - Name of the CA to enroll within the server
#
# enrollment section used to enroll intermediate CA with parent CA
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#
# tls section for secure socket connection
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
intermediate:
parentserver:
url:
caname:
enrollment:
hosts:
profile:
label:
tls:
certfiles:
client:
certfile:
keyfile:
#############################################################################
# CA configuration section
#
# Configure the number of incorrect password attempts are allowed for
# identities. By default, the value of 'passwordattempts' is 10, which
# means that 10 incorrect password attempts can be made before an identity get
# locked out.
#############################################################################
cfg:
identities:
passwordattempts: 10
###############################################################################
#
# Operations section
#
###############################################################################
operations:
# host and port for the operations server
listenAddress: 127.0.0.1:9444
# TLS configuration for the operations endpoint
tls:
# TLS enabled
enabled: false
# path to PEM encoded server certificate for the operations server
cert:
file:
# path to PEM encoded server key for the operations server
key:
file:
# require client certificate authentication to access all resources
clientAuthRequired: false
# paths to PEM encoded ca certificates to trust for client authentication
clientRootCAs:
files: []
###############################################################################
#
# Metrics section
#
###############################################################################
metrics:
# statsd, prometheus, or disabled
provider: disabled
# statsd configuration
statsd:
# network type: tcp or udp
network: udp
# statsd server address
address: 127.0.0.1:8125
# the interval at which locally cached counters and gauges are pushsed
# to statsd; timings are pushed immediately
writeInterval: 10s
# prefix is prepended to all emitted statsd merics
prefix: server

View file

@ -86,7 +86,7 @@ tls:
############################################################################# #############################################################################
ca: ca:
# Name of this CA # Name of this CA
name: org1-ecert-ca name: org2-ca
# Key file (is only used to import a private key into BCCSP) # Key file (is only used to import a private key into BCCSP)
keyfile: keyfile:
# Certificate file (default: ca-cert.pem) # Certificate file (default: ca-cert.pem)
@ -320,8 +320,8 @@ csr:
hosts: hosts:
- localhost - localhost
- 127.0.0.1 - 127.0.0.1
- org1-ecert-ca - org2-ca
- org1-ecert-ca.test-network.svc.cluster.local - org2-ca.test-network.svc.cluster.local
ca: ca:
expiry: 131400h expiry: 131400h
pathlength: 1 pathlength: 1

View file

@ -1,496 +0,0 @@
#############################################################################
# This is a configuration file for the fabric-ca-server command.
#
# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
# ------------------------------------------------
# Each configuration element can be overridden via command line
# arguments or environment variables. The precedence for determining
# the value of each element is as follows:
# 1) command line argument
# Examples:
# a) --port 443
# To set the listening port
# b) --ca.keyfile ../mykey.pem
# To set the "keyfile" element in the "ca" section below;
# note the '.' separator character.
# 2) environment variable
# Examples:
# a) FABRIC_CA_SERVER_PORT=443
# To set the listening port
# b) FABRIC_CA_SERVER_CA_KEYFILE="../mykey.pem"
# To set the "keyfile" element in the "ca" section below;
# note the '_' separator character.
# 3) configuration file
# 4) default value (if there is one)
# All default values are shown beside each element below.
#
# FILE NAME ELEMENTS
# ------------------
# The value of all fields whose name ends with "file" or "files" are
# name or names of other files.
# For example, see "tls.certfile" and "tls.clientauth.certfiles".
# The value of each of these fields can be a simple filename, a
# relative path, or an absolute path. If the value is not an
# absolute path, it is interpretted as being relative to the location
# of this configuration file.
#
#############################################################################
# Version of config file
version: 1.5.2
# Server's listening port (default: 7054)
port: 443
# Cross-Origin Resource Sharing (CORS)
cors:
enabled: false
origins:
- "*"
# Enables debug logging (default: false)
debug: false
# Size limit of an acceptable CRL in bytes (default: 512000)
crlsizelimit: 512000
#############################################################################
# TLS section for the server's listening port
#
# The following types are supported for client authentication: NoClientCert,
# RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven,
# and RequireAndVerifyClientCert.
#
# Certfiles is a list of root certificate authorities that the server uses
# when verifying client certificates.
#############################################################################
tls:
# Enable TLS (default: false)
enabled: true
# TLS for the server's listening port
certfile:
keyfile:
clientauth:
type: noclientcert
certfiles:
#############################################################################
# The CA section contains information related to the Certificate Authority
# including the name of the CA, which should be unique for all members
# of a blockchain network. It also includes the key and certificate files
# used when issuing enrollment certificates (ECerts) and transaction
# certificates (TCerts).
# The chainfile (if it exists) contains the certificate chain which
# should be trusted for this CA, where the 1st in the chain is always the
# root CA certificate.
#############################################################################
ca:
# Name of this CA
name: org2-tls-ca
# Key file (is only used to import a private key into BCCSP)
keyfile:
# Certificate file (default: ca-cert.pem)
certfile:
# Chain file
chainfile:
#############################################################################
# The gencrl REST endpoint is used to generate a CRL that contains revoked
# certificates. This section contains configuration options that are used
# during gencrl request processing.
#############################################################################
crl:
# Specifies expiration for the generated CRL. The number of hours
# specified by this property is added to the UTC time, the resulting time
# is used to set the 'Next Update' date of the CRL.
expiry: 24h
#############################################################################
# The registry section controls how the fabric-ca-server does two things:
# 1) authenticates enrollment requests which contain a username and password
# (also known as an enrollment ID and secret).
# 2) once authenticated, retrieves the identity's attribute names and
# values which the fabric-ca-server optionally puts into TCerts
# which it issues for transacting on the Hyperledger Fabric blockchain.
# These attributes are useful for making access control decisions in
# chaincode.
# There are two main configuration options:
# 1) The fabric-ca-server is the registry.
# This is true if "ldap.enabled" in the ldap section below is false.
# 2) An LDAP server is the registry, in which case the fabric-ca-server
# calls the LDAP server to perform these tasks.
# This is true if "ldap.enabled" in the ldap section below is true,
# which means this "registry" section is ignored.
#############################################################################
registry:
# Maximum number of times a password/secret can be reused for enrollment
# (default: -1, which means there is no limit)
maxenrollments: -1
# Contains identity information which is used when LDAP is disabled
identities:
- name: tlsadmin
pass: tlsadminpw
type: client
affiliation: ""
attrs:
hf.Registrar.Roles: "*"
hf.Registrar.DelegateRoles: "*"
hf.Revoker: true
hf.IntermediateCA: true
hf.GenCRL: true
hf.Registrar.Attributes: "*"
hf.AffiliationMgr: true
#############################################################################
# Database section
# Supported types are: "sqlite3", "postgres", and "mysql".
# The datasource value depends on the type.
# If the type is "sqlite3", the datasource value is a file name to use
# as the database store. Since "sqlite3" is an embedded database, it
# may not be used if you want to run the fabric-ca-server in a cluster.
# To run the fabric-ca-server in a cluster, you must choose "postgres"
# or "mysql".
#############################################################################
db:
type: sqlite3
datasource: fabric-ca-server.db
tls:
enabled: false
certfiles:
client:
certfile:
keyfile:
#############################################################################
# LDAP section
# If LDAP is enabled, the fabric-ca-server calls LDAP to:
# 1) authenticate enrollment ID and secret (i.e. username and password)
# for enrollment requests;
# 2) To retrieve identity attributes
#############################################################################
ldap:
# Enables or disables the LDAP client (default: false)
# If this is set to true, the "registry" section is ignored.
enabled: false
# The URL of the LDAP server
url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
# TLS configuration for the client connection to the LDAP server
tls:
certfiles:
client:
certfile:
keyfile:
# Attribute related configuration for mapping from LDAP entries to Fabric CA attributes
attribute:
# 'names' is an array of strings containing the LDAP attribute names which are
# requested from the LDAP server for an LDAP identity's entry
names: ['uid','member']
# The 'converters' section is used to convert an LDAP entry to the value of
# a fabric CA attribute.
# For example, the following converts an LDAP 'uid' attribute
# whose value begins with 'revoker' to a fabric CA attribute
# named "hf.Revoker" with a value of "true" (because the boolean expression
# evaluates to true).
# converters:
# - name: hf.Revoker
# value: attr("uid") =~ "revoker*"
converters:
- name:
value:
# The 'maps' section contains named maps which may be referenced by the 'map'
# function in the 'converters' section to map LDAP responses to arbitrary values.
# For example, assume a user has an LDAP attribute named 'member' which has multiple
# values which are each a distinguished name (i.e. a DN). For simplicity, assume the
# values of the 'member' attribute are 'dn1', 'dn2', and 'dn3'.
# Further assume the following configuration.
# converters:
# - name: hf.Registrar.Roles
# value: map(attr("member"),"groups")
# maps:
# groups:
# - name: dn1
# value: peer
# - name: dn2
# value: client
# The value of the user's 'hf.Registrar.Roles' attribute is then computed to be
# "peer,client,dn3". This is because the value of 'attr("member")' is
# "dn1,dn2,dn3", and the call to 'map' with a 2nd argument of
# "group" replaces "dn1" with "peer" and "dn2" with "client".
maps:
groups:
- name:
value:
#############################################################################
# Affiliations section. Fabric CA server can be bootstrapped with the
# affiliations specified in this section. Affiliations are specified as maps.
# For example:
# businessunit1:
# department1:
# - team1
# businessunit2:
# - department2
# - department3
#
# Affiliations are hierarchical in nature. In the above example,
# department1 (used as businessunit1.department1) is the child of businessunit1.
# team1 (used as businessunit1.department1.team1) is the child of department1.
# department2 (used as businessunit2.department2) and department3 (businessunit2.department3)
# are children of businessunit2.
# Note: Affiliations are case sensitive except for the non-leaf affiliations
# (like businessunit1, department1, businessunit2) that are specified in the configuration file,
# which are always stored in lower case.
#############################################################################
affiliations:
org1:
- department1
- department2
org2:
- department1
#############################################################################
# Signing section
#
# The "default" subsection is used to sign enrollment certificates;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#
# The "ca" profile subsection is used to sign intermediate CA certificates;
# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
# Note that "isca" is true, meaning that it issues a CA certificate.
# A maxpathlen of 0 means that the intermediate CA cannot issue other
# intermediate CA certificates, though it can still issue end entity certificates.
# (See RFC 5280, section 4.2.1.9)
#
# The "tls" profile subsection is used to sign TLS certificate requests;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#############################################################################
signing:
default:
authremote: {}
caconstraint: {}
expiry: 8760h
usage:
- signing
- key encipherment
- server auth
- client auth
- key agreement
profiles: null
###########################################################################
# Certificate Signing Request (CSR) section.
# This controls the creation of the root CA certificate.
# The expiration for the root CA certificate is configured with the
# "ca.expiry" field below, whose default value is "131400h" which is
# 15 years in hours.
# The pathlength field is used to limit CA certificate hierarchy as described
# in section 4.2.1.9 of RFC 5280.
# Examples:
# 1) No pathlength value means no limit is requested.
# 2) pathlength == 1 means a limit of 1 is requested which is the default for
# a root CA. This means the root CA can issue intermediate CA certificates,
# but these intermediate CAs may not in turn issue other CA certificates
# though they can still issue end entity certificates.
# 3) pathlength == 0 means a limit of 0 is requested;
# this is the default for an intermediate CA, which means it can not issue
# CA certificates though it can still issue end entity certificates.
###########################################################################
csr:
cn: fabric-ca-server
keyrequest:
algo: ecdsa
size: 256
names:
- C: US
ST: "North Carolina"
L:
O: Hyperledger
OU: Fabric
hosts:
- localhost
- 127.0.0.1
- org2-tls-ca
- org2-tls-ca.test-network.svc.cluster.local
ca:
expiry: 131400h
pathlength: 1
###########################################################################
# Each CA can issue both X509 enrollment certificate as well as Idemix
# Credential. This section specifies configuration for the issuer component
# that is responsible for issuing Idemix credentials.
###########################################################################
idemix:
# Specifies pool size for revocation handles. A revocation handle is an unique identifier of an
# Idemix credential. The issuer will create a pool revocation handles of this specified size. When
# a credential is requested, issuer will get handle from the pool and assign it to the credential.
# Issuer will repopulate the pool with new handles when the last handle in the pool is used.
# A revocation handle and credential revocation information (CRI) are used to create non revocation proof
# by the prover to prove to the verifier that her credential is not revoked.
rhpoolsize: 1000
# The Idemix credential issuance is a two step process. First step is to get a nonce from the issuer
# and second step is send credential request that is constructed using the nonce to the isuser to
# request a credential. This configuration property specifies expiration for the nonces. By default is
# nonces expire after 15 seconds. The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration).
nonceexpiration: 15s
# Specifies interval at which expired nonces are removed from datastore. Default value is 15 minutes.
# The value is expressed in the time.Duration format (see https://golang.org/pkg/time/#ParseDuration)
noncesweepinterval: 15m
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section is used to select which
# crypto library implementation to use
#############################################################################
bccsp:
default: SW
sw:
hash: SHA2
security: 256
filekeystore:
# The directory used for the software file-based keystore
keystore: msp/keystore
#############################################################################
# Multi CA section
#
# Each Fabric CA server contains one CA by default. This section is used
# to configure multiple CAs in a single server.
#
# 1) --cacount <number-of-CAs>
# Automatically generate <number-of-CAs> non-default CAs. The names of these
# additional CAs are "ca1", "ca2", ... "caN", where "N" is <number-of-CAs>
# This is particularly useful in a development environment to quickly set up
# multiple CAs. Note that, this config option is not applicable to intermediate CA server
# i.e., Fabric CA server that is started with intermediate.parentserver.url config
# option (-u command line option)
#
# 2) --cafiles <CA-config-files>
# For each CA config file in the list, generate a separate signing CA. Each CA
# config file in this list MAY contain all of the same elements as are found in
# the server config file except port, debug, and tls sections.
#
# Examples:
# fabric-ca-server start -b admin:adminpw --cacount 2
#
# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
# --cafiles ca/ca2/fabric-ca-server-config.yaml
#
#############################################################################
cacount:
cafiles:
#############################################################################
# Intermediate CA section
#
# The relationship between servers and CAs is as follows:
# 1) A single server process may contain or function as one or more CAs.
# This is configured by the "Multi CA section" above.
# 2) Each CA is either a root CA or an intermediate CA.
# 3) Each intermediate CA has a parent CA which is either a root CA or another intermediate CA.
#
# This section pertains to configuration of #2 and #3.
# If the "intermediate.parentserver.url" property is set,
# then this is an intermediate CA with the specified parent
# CA.
#
# parentserver section
# url - The URL of the parent server
# caname - Name of the CA to enroll within the server
#
# enrollment section used to enroll intermediate CA with parent CA
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#
# tls section for secure socket connection
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
intermediate:
parentserver:
url:
caname:
enrollment:
hosts:
profile:
label:
tls:
certfiles:
client:
certfile:
keyfile:
#############################################################################
# CA configuration section
#
# Configure the number of incorrect password attempts are allowed for
# identities. By default, the value of 'passwordattempts' is 10, which
# means that 10 incorrect password attempts can be made before an identity get
# locked out.
#############################################################################
cfg:
identities:
passwordattempts: 10
###############################################################################
#
# Operations section
#
###############################################################################
operations:
# host and port for the operations server
listenAddress: 127.0.0.1:9444
# TLS configuration for the operations endpoint
tls:
# TLS enabled
enabled: false
# path to PEM encoded server certificate for the operations server
cert:
file:
# path to PEM encoded server key for the operations server
key:
file:
# require client certificate authentication to access all resources
clientAuthRequired: false
# paths to PEM encoded ca certificates to trust for client authentication
clientRootCAs:
files: []
###############################################################################
#
# Metrics section
#
###############################################################################
metrics:
# statsd, prometheus, or disabled
provider: disabled
# statsd configuration
statsd:
# network type: tcp or udp
network: udp
# statsd server address
address: 127.0.0.1:8125
# the interval at which locally cached counters and gauges are pushsed
# to statsd; timings are pushed immediately
writeInterval: 10s
# prefix is prepended to all emitted statsd merics
prefix: server

View file

@ -9,7 +9,7 @@ Launching fabric-rest-sample application:
✅ - Constructing fabric-rest-sample connection profiles ... ✅ - Constructing fabric-rest-sample connection profiles ...
✅ - Starting fabric-rest-sample ... ✅ - Starting fabric-rest-sample ...
The fabric-rest-sample has started. See https://github.com/hyperledgendary/fabric-rest-sample/tree/main/asset-transfer-basic/rest-api-typescript#rest-api for additional usage. The fabric-rest-sample has started. See https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/rest-api-typescript for additional usage.
To access the endpoint: To access the endpoint:
export SAMPLE_APIKEY=97834158-3224-4CE7-95F9-A148C886653E export SAMPLE_APIKEY=97834158-3224-4CE7-95F9-A148C886653E
@ -37,7 +37,7 @@ $ curl -s --header "X-Api-Key: ${SAMPLE_APIKEY}" http://localhost/api/assets | j
} }
] ]
$ open https://github.com/hyperledgendary/fabric-rest-sample/tree/main/asset-transfer-basic/rest-api-typescript#rest-api $ open https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/rest-api-typescript
``` ```
## Guide for Gateway Client Applications ## Guide for Gateway Client Applications

View file

@ -19,53 +19,54 @@ $ ./network up
Launching network "test-network": Launching network "test-network":
... ...
✅ - Launching TLS CAs ... ✅ - Initializing TLS certificate Issuers ...
✅ - Enrolling bootstrap TLS CA users ...
✅ - Registering and enrolling ECert CA bootstrap users ...
✅ - Launching ECert CAs ... ✅ - Launching ECert CAs ...
✅ - Enrolling bootstrap ECert CA users ... ✅ - Enrolling bootstrap ECert CA users ...
... ...
🏁 - Network is ready. 🏁 - Network is ready.
``` ```
## [Planning for a CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/ca-deploy-topology.html#planning-for-a-ca) ## [Planning for a CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/ca-deploy-topology.html#planning-for-a-ca)
Setting up a CA framework is one of the more daunting aspects of a Fabric installation. There is an incredible amount Setting up a CA framework is one of the more daunting aspects of a Fabric installation. There is an incredible amount
of flexibility possible with the Fabric CA architecture, so to keep things straightforward we have opted to aim for a of flexibility possible with the Fabric CA architecture, so to keep things straightforward we have opted to aim for a
simplified, but realistic CA deployment illustrating the key touch points with Kubernetes: simplified, but realistic CA deployment illustrating key touch points with Kubernetes:
- Each organization maintains distinct, [independent volumes](../kube/pv-fabric-org0.yaml) for the storage of MSP and - Each organization maintains distinct, [independent volumes](../kube/pv-fabric-org0.yaml) for the storage of MSP and
TLS certificates. This forces the consortium organizer to plan for the distribution of _public_ certificates to node certificates. This forces the consortium organizer to plan for the distribution of _public_ certificates to
member organizations, while maintaining an independent, secret storage location for _private_ signing keys. member organizations, while maintaining an independent, secret storage location for _private_ signing keys.
- Each organization maintains two distinct, separate CA instances : one dedicated to [TLS](../kube/org0/org0-tls-ca.yaml) - This guide simplifies the storage and organization of Fabric certificates into two distinct flows. For securing
Certificate Signing Requests, and a second process dedicated to [ECert](../kube/org0/org0-ecert-ca.yaml) Enrollments inter-node communication with TLS, [cert-manager](https://cert-manager.io) is responsible for the lifecycle of issuing,
and identity MSPs. renewing, and revoking SSL certificates and keys as native Kubernetes `Certificate` resources. Complementing the
SSL certificate lifecycle is a set of fabric-CAs responsible for fulfilling Fabric [ECert](../kube/org0/org0-ca.yaml)
Enrollments and identities.
- Certificate organization and [Folder Structure](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/use_CA.html#folder-structure-for-your-org-and-node-admin-identities) - MSP Certificate organization and [Folder Structure](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/use_CA.html#folder-structure-for-your-org-and-node-admin-identities)
strictly adheres to the best practices and guidelines recommended by the CA Deployment Guide. strictly adheres to the best practices and guidelines recommended by the CA Deployment Guide.
- The `cryptogen` anti-pattern is **strictly forbidden**. All TLS and MSP enrollments are constructed using the CA - The `cryptogen` anti-pattern is **strictly forbidden**. All MSP enrollments are constructed using the CA
registration and enrollment REST services, coordinated by calls to `fabric-ca-client` running directly on the registration and enrollment REST services, coordinated by calls to `fabric-ca-client`. At runtime, the ca-client
CA pods. When working with certificates, the fabric CA client ONLY has visibility to the organization's local volume ONLY has visibility to the organization's shared volume mount.
storage.
- TLS CA configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-tls-ca-server` - TLS Certificates are stored and organized within the cluster as a series of `Certificate` resources with associated
Kube `Secret` and volume mounts. Service pods mount the node TLS key pair and CA certificate at `/var/hyperledger/fabric/config/tls`.
Each organization in the network maintains an independent [CA `Issuer`](https://cert-manager.io/docs/configuration/ca/)
endorsed by a system-wide, self-signed root CA.
- ECert CA configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-ca-server` - Each organization in the network maintains an independent fabric CA instance, with configuration and certificates
stored in each org's persistent volume at `/var/hyperledger/fabric-ca-server`.
- fabric-ca-client configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-ca-client` - fabric-ca-client configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-ca-client`
- ECert and MSP data structures are maintained in each org's persistent volume at `/var/hyperledger/fabric/organizations` - ECert and MSP enrollment structures are maintained in each org's persistent volume at `/var/hyperledger/fabric/organizations`
@ -77,11 +78,6 @@ simplified, but realistic CA deployment illustrating the key touch points with K
and/or alternate signing chains backed by formal (e.g. letsencrypt, Thawte, Verisign, etc.) certificate authorities. and/or alternate signing chains backed by formal (e.g. letsencrypt, Thawte, Verisign, etc.) certificate authorities.
- **_Dual Headed CAs_** : In practice, juggling two distinct deployments between TLS and ECert servers adds little
functional value. It would be nice to simplify the configuration, deployment, and bootstrapping scripts such that
each org manages a single, dual-headed CA capable of responding to both TLS as well as ECert enrollmnent rerquests.
- **_Time-Bomb Certificates_** : By default the certificates issued by the test network are valid for 1 (one) year. For - **_Time-Bomb Certificates_** : By default the certificates issued by the test network are valid for 1 (one) year. For
lightweight or adhoc testing, this is fine. But when applied to production deployments, certificate expiry is a lightweight or adhoc testing, this is fine. But when applied to production deployments, certificate expiry is a
real operational challenge. For instance, it is possible to soft-lock a Fabric network when all system certificates real operational challenge. For instance, it is possible to soft-lock a Fabric network when all system certificates
@ -103,86 +99,41 @@ simplified, but realistic CA deployment illustrating the key touch points with K
The [sequence of activities](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#what-order-should-i-deploy-the-cas) The [sequence of activities](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#what-order-should-i-deploy-the-cas)
necessary to bring up a CA infrastructure is well documented by the CA Deployment Guide: necessary to bring up a CA infrastructure is well documented by the CA Deployment Guide:
1. [Deploy the TLS CAs](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-the-tls-ca) 1. [Deploy TLS CA Issuers](#deploy-tls-ca-issuers)
1. [Configure the TLS CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-tls-ca-server-configuration)
1. [Launch the TLS CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-tls-ca-server)
1. [Enroll the TLS CA Bootstrap Admin Users](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#enroll-bootstrap-user-with-tls-ca)
1. [Deploy the Organization CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-an-organization-ca) 1. [Deploy the Organization CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-an-organization-ca)
1. [Register and enroll the org CA bootstrap identity with the TLS CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#register-and-enroll-the-organization-ca-bootstrap-identity-with-the-tls-ca)
1. [Configure the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-ca-server-configuration) 1. [Configure the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-ca-server-configuration)
1. [Launch the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-ca-server) 1. [Launch the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-ca-server)
1. [Enroll the ECert CA Bootstrap / Admin User](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#enroll-the-ca-admin) 1. [Enroll the ECert CA Bootstrap / Admin User](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#enroll-the-ca-admin)
## [Deploy the TLS CAs](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-the-tls-ca) ## Deploy TLS CA Issuers
### [Configure the TLS CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-tls-ca-server-configuration) ```
✅ - Initializing TLS certificate Issuers ...
While the CA guide suggests running the `fabric-ca-server` binary to generate a default configuration file, for the ...
test network we've skipped this step and have added a [config/fabric-tls-ca-server-config.yaml](../config/org0/fabric-tls-ca-server-config.yaml)
to the top level of this project.
Changes have been made to reflect:
- `port: 443` binds all traffic to the default HTTPS port
- `tls.enabled: true` enables TLS for registration and enrollment requests
- `ca.name: <service-name>` matches the Kubernetes `Service` host alias
- `csr.hosts:` includes host aliases for accessing the CA with Kube DNS
Prior to launching the CA, for each org we create a configmap including the TLS CA server yaml:
```shell
kubectl -n test-network create configmap org0-config --from-file=config/org0
kubectl -n test-network create configmap org1-config --from-file=config/org1
kubectl -n test-network create configmap org2-config --from-file=config/org2
``` ```
The Kubernetes Test Network relies on [cert-manager](https://cert-manager.io) to issue, renew, and revoke TLS
certificates for network endpoints. Before launching peers, orderers, and chaincode pods, each node must
have a corresponding [`Certificate`](https://cert-manager.io/docs/usage/certificate/) generated by a cert manager [CA
`Issuer`](https://cert-manager.io/docs/configuration/ca/), stored in Kubernetes and exposed as a kube `Secret` at
runtime.
### [Launch the TLS CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-tls-ca-server) In the test network, the root TLS certificate is automatically generated by requesting a self-signed ECDSA key pair.
In turn, the root key is used to create a series of CA `Issuers`, one per member organization participating in the
blockchain:
```shell ```
✅ - Launching TLS CAs ... # Use the self-signing issuer to generate three Issuers, one for each org:
kubectl -n test-network apply -f kube/org0/org0-tls-cert-issuer.yaml
kubectl -n test-network apply -f kube/org1/org1-tls-cert-issuer.yaml
kubectl -n test-network apply -f kube/org2/org2-tls-cert-issuer.yaml
``` ```
For each org we create a Kube Deployment and Service, ensuring that the org config Each organization's CA `Issuer` will be used to construct a TLS `Certificate` for each node in the network. At
map and persistent volume maps to the correct location on disk. runtime, the deployment pods will mount the certificate contents (`tls.key`, `tls.pem`, and `ca.pem`) as a kube
secrets mounted at `/var/hyperledger/fabric/config/tls`.
```shell
kubectl -n test-network apply -f kube/org0/org0-tls-ca.yaml
kubectl -n test-network apply -f kube/org1/org1-tls-ca.yaml
kubectl -n test-network apply -f kube/org2/org2-tls-ca.yaml
```
As a side-effect of bootstrapping the TLS CA, each storage volume will include a self-signed certificate
pair to serve as the **Root TLS Certificate**. Pay special attention to this path, as it will be used extensively
to verify the TLS host name of all services within the organization:
```shell
${FABRIC_CA_CLIENT_HOME}/tls-root-cert/tls-ca-cert.pem
```
### [Enroll the TLS CA Bootstrap Admin Users](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#enroll-bootstrap-user-with-tls-ca)
```shell
✅ - Enrolling bootstrap TLS CA users ...
```
After the TLS server is running, we need to enroll the bootstrap admin user with the CA. This admin user will
then be employed to fulfill a Certificate Signing request for the ECert CA servers, allowing for full host
verification when connecting to the ECert CAs via https.
To enroll the bootstrap TLS CA users, each org runs within the TLS CA pod:
```shell
fabric-ca-client enroll \
--url https://'$auth'@'${tlsca}' \
--tls.certfiles $FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem \
--csr.hosts '${tlsca}' \
--mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp
```
The --mspdir output of this command is a set of certificates for use with the ECert CA. This enrollment MSP
will be used to register and enroll the ECert bootstrap user.
## [Deploy the Organization CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-an-organization-ca) ## [Deploy the Organization CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#deploy-an-organization-ca)
@ -192,47 +143,16 @@ Before we can set up the peers, orderers, and channels, we will need to bootstra
for each org in the network. for each org in the network.
### [Register and enroll the organization CA bootstrap identity with the TLS CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#register-and-enroll-the-organization-ca-bootstrap-identity-with-the-tls-ca)
```shell
✅ - Registering and enrolling ECert CA bootstrap users ...
```
The TLS CA can be used to fulfill a Certificate Signing Request on behalf of each organization's ECert CA.
```shell
fabric-ca-client register \
--id.name rcaadmin \
--id.secret rcaadminpw \
--url https://'${tlsca}' \
--tls.certfiles $FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem \
--mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp
fabric-ca-client enroll \
--url https://'${tlsauth}'@'${tlsca}' \
--tls.certfiles $FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem \
--csr.hosts '${ecertca}' \
--mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/rcaadmin/msp
```
**Important**: The output from this enrollment includes the ECert CA's public certificate and private signing keys.
When the ECert CA pod is launched, the server configuration references the `tls.certfile` and `tls.keyfile` attributes
by specifying `FABRIC_CA_SERVER_TLS_CERTFILE` and `FABRIC_CA_SERVER_TLS_KEYFILE` environment in the pod's environment.
### [Configure the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-ca-server-configuration) ### [Configure the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#modify-the-ca-server-configuration)
When launching the ECert CA pods, both the org volume shares and org config maps are made available via volume shares. When launching the ECert CA pods, both the org volume shares and org config maps are made available via volume shares.
The [fabric-ecert-ca-server.yaml](../config/org0/fabric-ecert-ca-server-config.yaml) includes overrides for: The [fabric-ecert-ca-server.yaml](../config/org0/fabric-ca-server-config.yaml) includes overrides for:
- `port: 443` binds all traffic to the default HTTPS port - `port: 443` binds all traffic to the default HTTPS port
- `tls.enabled: true` enables TLS for registration and enrollment requests - `tls.enabled: true` enables TLS for registration and enrollment requests
- `ca.name: <service-name>` matches the Kubernetes `Service` host alias - `ca.name: <service-name>` matches the Kubernetes `Service` host alias
- `csr.hosts:` includes host aliases for accessing the CA with Kube DNS - `csr.hosts:` includes host aliases for accessing the CA with Kube DNS
In addition, pay special attention to the location of the `FABRIC_CA_SERVER_TLS_CERTFILE` and `FABRIC_CA_SERVER_TLS_KEYFILE`
environment variables in the [ECert deployment descriptor](../kube/org0/org0-ecert-ca.yaml). These variables
reference the TLS certificate authority and signing keys as generated by the admin bootstrap enrollment.
### [Launch the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-ca-server) ### [Launch the ECert CA Servers](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#start-the-ca-server)
```shell ```shell
@ -240,9 +160,9 @@ reference the TLS certificate authority and signing keys as generated by the adm
``` ```
```shell ```shell
kubectl -n test-network apply -f kube/org0/org0-ecert-ca.yaml kubectl -n test-network apply -f kube/org0/org0-ca.yaml
kubectl -n test-network apply -f kube/org1/org1-ecert-ca.yaml kubectl -n test-network apply -f kube/org1/org1-ca.yaml
kubectl -n test-network apply -f kube/org2/org2-ecert-ca.yaml kubectl -n test-network apply -f kube/org2/org2-ca.yaml
``` ```
- [x] Note: The `rcaadmin` enrollment's `cert.pem` and `key.pem` locations are specified in the ecert CA's k8s deployment as environment variables. - [x] Note: The `rcaadmin` enrollment's `cert.pem` and `key.pem` locations are specified in the ecert CA's k8s deployment as environment variables.
@ -259,7 +179,7 @@ local MSP certificate structure for all of the nodes in our test network.
```shell ```shell
fabric-ca-client enroll \ fabric-ca-client enroll \
--url https://'${auth}'@'${ecert_ca}' \ --url https://'${auth}'@'${ecert_ca}' \
--tls.certfiles $FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem \ --tls.certfiles /var/hyperledger/fabric/config/tls/ca.pem \
--mspdir $FABRIC_CA_CLIENT_HOME/'${ecert_ca}'/rcaadmin/msp --mspdir $FABRIC_CA_CLIENT_HOME/'${ecert_ca}'/rcaadmin/msp
``` ```
@ -268,13 +188,10 @@ local MSP certificate structure for all of the nodes in our test network.
After the CAs have been deployed, each org in the Kube namespace includes: After the CAs have been deployed, each org in the Kube namespace includes:
- One TLS CA `Service`, forwarding internal traffic from https://orgN-tls-ca to the TLS CA - One TLS CA `Issuer` and issuer `Certificate`
- One TLS CA `Deployment`
- One TLS CA `Pod`
- One ECert CA `Service`, forwarding internal traffic from https://orgN-ecert-ca to the ECert CA - One ECert CA `Service`, forwarding internal traffic from https://orgN-ecert-ca to the ECert CA
- One ECert CA `Deployment` - One ECert CA `Deployment`
- One ECert CA `Pod` - One ECert CA `Pod`
- One TLS CA admin bootstrap user `tlsadmin` enrollment and TLS root certificate.
- One ECert CA admin bootstrap user `rcaadmin` enrollment and MSP root certificate. - One ECert CA admin bootstrap user `rcaadmin` enrollment and MSP root certificate.

View file

@ -74,9 +74,9 @@ of a remote `kubectl` into a local archive files. These files are then mounted
constructing the `msp-config` config map: constructing the `msp-config` config map:
```shell ```shell
kubectl -n $NS exec deploy/org0-ecert-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/ordererOrganizations/org0.example.com/msp > msp/msp-org0.example.com.tgz kubectl -n $NS exec deploy/org0-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/ordererOrganizations/org0.example.com/msp > msp/msp-org0.example.com.tgz
kubectl -n $NS exec deploy/org1-ecert-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/msp > msp/msp-org1.example.com.tgz kubectl -n $NS exec deploy/org1-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/msp > msp/msp-org1.example.com.tgz
kubectl -n $NS exec deploy/org2-ecert-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/msp > msp/msp-org2.example.com.tgz kubectl -n $NS exec deploy/org2-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/msp > msp/msp-org2.example.com.tgz
kubectl -n $NS delete configmap msp-config || true kubectl -n $NS delete configmap msp-config || true
kubectl -n $NS create configmap msp-config --from-file=msp/``` kubectl -n $NS create configmap msp-config --from-file=msp/```

View file

@ -56,7 +56,7 @@ It is important that applications connect to the `org2-peer-gateway-svc` or `org
The solution is to add the additional servicename to the hosts field in the SAN section of the TLS certificate. As an example here is the command that is used to create the TLS certificate for org1-peer1. Note the The solution is to add the additional servicename to the hosts field in the SAN section of the TLS certificate. As an example here is the command that is used to create the TLS certificate for org1-peer1. Note the
```bash ```bash
fabric-ca-client enroll --url https://org1-peer1:peerpw@org1-ecert-ca --csr.hosts org1-peer1,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp fabric-ca-client enroll --url https://org1-peer1:peerpw@org1-ca --csr.hosts org1-peer1,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp
``` ```
## Summary ## Summary

View file

@ -195,4 +195,3 @@ Example configurations for common cloud vendors:
### Azure ### Azure
## Next : [Fabric Certificate Authorities](CA.md) ## Next : [Fabric Certificate Authorities](CA.md)

View file

@ -6,11 +6,9 @@ providing a study guide for operational patterns, the test-network provided a ba
the Fabric community to quickly get up to speed with a working, local system, author smart contracts, and develop the Fabric community to quickly get up to speed with a working, local system, author smart contracts, and develop
simple blockchain applications. simple blockchain applications.
While test-network provided a solid foundation for casual Fabric development, the over-reliance on As a supplement to the docker-compose based test-network, this guide presents an equivalent Fabric network
[Docker Compose](https://docs.docker.com/compose/) introduced tremendous, non-trivial complexity when transitioning suitable for running sample applications and chaincode, developing Gateway and Chaincode-as-a-Service applications,
applications to production. Without belaboring the many issues and anti-patterns present in the Compose-based and harmonizing CI and deployment flows with a unified container framework - Kubernetes.
test network, we'll submit that the best path forward is to _align_ the development and production patterns around a
common orchestration framework - Kubernetes.
Similar to Fabric, Kubernetes introduces a steep learning curve and presents a dizzying array of operational Similar to Fabric, Kubernetes introduces a steep learning curve and presents a dizzying array of operational
flexibility. In this guide, we'll outline the design considerations in the [`./network`](../network) flexibility. In this guide, we'll outline the design considerations in the [`./network`](../network)
@ -25,8 +23,7 @@ _Ahoy!_
The Kube test network establishes as consortium among a dedicated ordering organization and two peer organizations. The Kube test network establishes as consortium among a dedicated ordering organization and two peer organizations.
Participation in the network is managed over a channel, and transactions are committed to the blockchain ledgers by Participation in the network is managed over a channel, and transactions are committed to the blockchain ledgers by
invoking the [asset-transfer-basic](https://github.com/hyperledgendary/fabric-ccaas-asset-transfer-basic) invoking the [asset-transfer-basic](https://github.com/hyperledgendary/fabric-ccaas-asset-transfer-basic)
_Chaincode-as-a-Service_ running in a shared Kubernetes namespace. Each organization maintains indepedendent TLS _Chaincode-as-a-Service_ running in a shared Kubernetes namespace.
and ECert CAs for management of local, channel, and user MSP contexts.
![Test Network](images/test-network.png) ![Test Network](images/test-network.png)
@ -37,7 +34,7 @@ and ECert CAs for management of local, channel, and user MSP contexts.
- [Working with Kubernetes](KUBERNETES.md) - [Working with Kubernetes](KUBERNETES.md)
- [Certificate Authorities](CA.md) - [Certificate Authorities](CA.md)
- [Planning for a CA](CA.md#planning-for-a-ca) - [Planning for a CA](CA.md#planning-for-a-ca)
- [Deploy the TLS CAs](CA.md#deploy-the-tls-cas) - [Deploy the TLS CAs](CA.md#deploy-tls-ca-issuers)
- [Deploy the ECert CAs](CA.md#deploy-the-organization-ca) - [Deploy the ECert CAs](CA.md#deploy-the-organization-ca)
- [Launching the Test Network](TEST_NETWORK.md) - [Launching the Test Network](TEST_NETWORK.md)
- [Registering and Enrolling Identities](CA.md#registering-and-enrolling-identities) - [Registering and Enrolling Identities](CA.md#registering-and-enrolling-identities)

View file

@ -44,44 +44,30 @@ the target usage in the network.
For example, the ordering organization sets up the node local MSP with: For example, the ordering organization sets up the node local MSP with:
```shell ```shell
# Each identity in the network needs a registration and enrollment. # Each identity in the network needs a registration and enrollment.
fabric-ca-client register --id.name org0-orderer1 --id.secret ordererpw --id.type orderer --url https://org0-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ecert-ca/rcaadmin/msp fabric-ca-client register --id.name org0-orderer1 --id.secret ordererpw --id.type orderer --url https://org0-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ca/rcaadmin/msp
fabric-ca-client register --id.name org0-orderer2 --id.secret ordererpw --id.type orderer --url https://org0-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ecert-ca/rcaadmin/msp fabric-ca-client register --id.name org0-orderer2 --id.secret ordererpw --id.type orderer --url https://org0-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ca/rcaadmin/msp
fabric-ca-client register --id.name org0-orderer3 --id.secret ordererpw --id.type orderer --url https://org0-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ecert-ca/rcaadmin/msp fabric-ca-client register --id.name org0-orderer3 --id.secret ordererpw --id.type orderer --url https://org0-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ca/rcaadmin/msp
fabric-ca-client register --id.name org0-admin --id.secret org0adminpw --id.type admin --url https://org0-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ecert-ca/rcaadmin/msp --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*,hf.Revoker=true,hf.GenCRL=true,admin=true:ecert,abac.init=true:ecert" fabric-ca-client register --id.name org0-admin --id.secret org0adminpw --id.type admin --url https://org0-ca --mspdir $FABRIC_CA_CLIENT_HOME/org0-ca/rcaadmin/msp --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*,hf.Revoker=true,hf.GenCRL=true,admin=true:ecert,abac.init=true:ecert"
fabric-ca-client enroll --url https://org0-orderer1:ordererpw@org0-ecert-ca --csr.hosts org0-orderer1 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp fabric-ca-client enroll --url https://org0-orderer1:ordererpw@org0-ca --csr.hosts org0-orderer1 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp
fabric-ca-client enroll --url https://org0-orderer2:ordererpw@org0-ecert-ca --csr.hosts org0-orderer2 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp fabric-ca-client enroll --url https://org0-orderer2:ordererpw@org0-ca --csr.hosts org0-orderer2 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp
fabric-ca-client enroll --url https://org0-orderer3:ordererpw@org0-ecert-ca --csr.hosts org0-orderer3 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp fabric-ca-client enroll --url https://org0-orderer3:ordererpw@org0-ca --csr.hosts org0-orderer3 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp
fabric-ca-client enroll --url https://org0-admin:org0adminpw@org0-ecert-ca --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/users/Admin@org0.example.com/msp fabric-ca-client enroll --url https://org0-admin:org0adminpw@org0-ca --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/users/Admin@org0.example.com/msp
# Each node in the network needs a TLS registration and enrollment.
fabric-ca-client register --id.name org0-orderer1 --id.secret ordererpw --id.type orderer --url https://org0-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp
fabric-ca-client register --id.name org0-orderer2 --id.secret ordererpw --id.type orderer --url https://org0-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp
fabric-ca-client register --id.name org0-orderer3 --id.secret ordererpw --id.type orderer --url https://org0-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp
fabric-ca-client enroll --url https://org0-orderer1:ordererpw@org0-tls-ca --csr.hosts org0-orderer1 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls
fabric-ca-client enroll --url https://org0-orderer2:ordererpw@org0-tls-ca --csr.hosts org0-orderer2 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls
fabric-ca-client enroll --url https://org0-orderer3:ordererpw@org0-tls-ca --csr.hosts org0-orderer3 --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls
# Copy the TLS signing keys to a fixed path for convenience when starting the orderers.
cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/keystore/server.key
cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/keystore/server.key
cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/keystore/server.key
# Create an MSP config.yaml (why is this not generated by the enrollment by fabric-ca-client?) # Create an MSP config.yaml (why is this not generated by the enrollment by fabric-ca-client?)
echo "NodeOUs: echo "NodeOUs:
Enable: true Enable: true
ClientOUIdentifier: ClientOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: client OrganizationalUnitIdentifier: client
PeerOUIdentifier: PeerOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: peer OrganizationalUnitIdentifier: peer
AdminOUIdentifier: AdminOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: admin OrganizationalUnitIdentifier: admin
OrdererOUIdentifier: OrdererOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: orderer" > /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml OrganizationalUnitIdentifier: orderer" > /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml
cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp/config.yaml cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp/config.yaml
@ -173,35 +159,28 @@ cat kube/org2/org2-peer1.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g'
cat kube/org2/org2-peer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f - cat kube/org2/org2-peer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
``` ```
- [x] Pro tip: Run an early-release Fabric build by setting `TEST_NETWORK_FABRIC_VERSION=2.4.0-beta`
## Next Steps : ## Next Steps :
After the peers and orderers have started, the Kube namespace includes pods, deployments, and service bindings for: After the peers and orderers have started, the Kube namespace includes pods, deployments, and service bindings for:
- Org0 (org0.example.com): - Org0 (org0.example.com):
- TLS Certificate Authority : https://org0-tls-ca - ECert Certificate Authority : https://org0-ca
- ECert Certificate Authority : https://org0-ecert-ca
- Orderer1 : grpcs://org0-orderer1 - Orderer1 : grpcs://org0-orderer1
- Orderer2 : grpcs://org0-orderer2 - Orderer2 : grpcs://org0-orderer2
- Orderer3 : grpcs://org0-orderer3 - Orderer3 : grpcs://org0-orderer3
- Org1 (org1.example.com): - Org1 (org1.example.com):
- TLS Certificate Authority : https://org1-tls-ca - ECert Certificate Authority : https://org1-ca
- ECert Certificate Authority : https://org1-ecert-ca
- Peer Node 1 : grpcs://org1-peer1 - Peer Node 1 : grpcs://org1-peer1
- Peer Node 2 : grpcs://org1-peer2 - Peer Node 2 : grpcs://org1-peer2
- Org2 (org2.example.com): - Org2 (org2.example.com):
- TLS Certificate Authority : https://org2-tls-ca - ECert Certificate Authority : https://org2-ca
- ECert Certificate Authority : https://org2-ecert-ca
- Peer Node 1 : grpcs://org2-peer1 - Peer Node 1 : grpcs://org2-peer1
- Peer Node 2 : grpcs://org2-peer2 - Peer Node 2 : grpcs://org2-peer2
### Next : [Working With Channels](CHANNELS.md) ### Next : [Working With Channels](CHANNELS.md)

View file

@ -27,30 +27,30 @@ data:
"Org1": { "Org1": {
"mspid": "Org1MSP", "mspid": "Org1MSP",
"peers": [ "peers": [
"org1-peer1" "org1-peers"
], ],
"certificateAuthorities": [ "certificateAuthorities": [
"org1-ecert" "org1-ca"
] ]
} }
}, },
"peers": { "peers": {
"org1-peer1": { "org1-peers": {
"url": "grpcs://org1-peer1:7051", "url": "grpcs://org1-peer-gateway-svc:7051",
"tlsCACerts": { "tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\\nMIICvzCCAmWgAwIBAgIULJGws7jbEY6ruSgDuvi9L7VphvIwCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkyMDE2MDkwMFoXDTIyMDkyMDE2MTQwMFowYDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjENMAsGA1UECxMEcGVlcjETMBEGA1UEAxMKb3JnMS1wZWVyMTBZMBMG\\nByqGSM49AgEGCCqGSM49AwEHA0IABL9e3GZBf1MeoObGxwSHkcgDEjMo+/13Qc4u\\nfSG2MKrveHBIEA4MRkHNqd+sTjoz0/1B15y2n+RiPo8uJvlyC/CjgfQwgfEwDgYD\\nVR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV\\nHRMBAf8EAjAAMB0GA1UdDgQWBBSeytspiXlEzMAsnF9/wxqc9fydETAfBgNVHSME\\nGDAWgBQwru1VH0OwH3dxfPdD8w74ZIlLRzAVBgNVHREEDjAMggpvcmcxLXBlZXIx\\nMFsGCCoDBAUGBwgBBE97ImF0dHJzIjp7ImhmLkFmZmlsaWF0aW9uIjoiIiwiaGYu\\nRW5yb2xsbWVudElEIjoib3JnMS1wZWVyMSIsImhmLlR5cGUiOiJwZWVyIn19MAoG\\nCCqGSM49BAMCA0gAMEUCIQDJEjPxceCfXU5B/emrHE4JbEzrZKxLVViBWCNMsHiR\\nFgIgY+8jsvr3rlBPkpRhl8CtT2DgaP7iWvovtMYsPKhLAqk=\\n-----END CERTIFICATE-----\\n" "pem": "-----BEGIN CERTIFICATE-----\\nMIICvzCCAmWgAwIBAgIULJGws7jbEY6ruSgDuvi9L7VphvIwCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkyMDE2MDkwMFoXDTIyMDkyMDE2MTQwMFowYDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjENMAsGA1UECxMEcGVlcjETMBEGA1UEAxMKb3JnMS1wZWVyMTBZMBMG\\nByqGSM49AgEGCCqGSM49AwEHA0IABL9e3GZBf1MeoObGxwSHkcgDEjMo+/13Qc4u\\nfSG2MKrveHBIEA4MRkHNqd+sTjoz0/1B15y2n+RiPo8uJvlyC/CjgfQwgfEwDgYD\\nVR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV\\nHRMBAf8EAjAAMB0GA1UdDgQWBBSeytspiXlEzMAsnF9/wxqc9fydETAfBgNVHSME\\nGDAWgBQwru1VH0OwH3dxfPdD8w74ZIlLRzAVBgNVHREEDjAMggpvcmcxLXBlZXIx\\nMFsGCCoDBAUGBwgBBE97ImF0dHJzIjp7ImhmLkFmZmlsaWF0aW9uIjoiIiwiaGYu\\nRW5yb2xsbWVudElEIjoib3JnMS1wZWVyMSIsImhmLlR5cGUiOiJwZWVyIn19MAoG\\nCCqGSM49BAMCA0gAMEUCIQDJEjPxceCfXU5B/emrHE4JbEzrZKxLVViBWCNMsHiR\\nFgIgY+8jsvr3rlBPkpRhl8CtT2DgaP7iWvovtMYsPKhLAqk=\\n-----END CERTIFICATE-----\\n"
}, },
"grpcOptions": { "grpcOptions": {
"grpc-wait-for-ready-timeout": 100000, "grpc-wait-for-ready-timeout": 100000,
"ssl-target-name-override": "org1-peer1", "ssl-target-name-override": "org1-peer-gateway-svc",
"hostnameOverride": "org1-peer1" "hostnameOverride": "org1-peer-gateway-svc"
} }
} }
}, },
"certificateAuthorities": { "certificateAuthorities": {
"org1-ecert-ca": { "org1-ca": {
"url": "https://org1-ecert-ca", "url": "https://org1-ca",
"caName": "org1-ecert-ca", "caName": "org1-ca",
"tlsCACerts": { "tlsCACerts": {
"pem": "TODO" "pem": "TODO"
}, },
@ -103,29 +103,29 @@ data:
"Org2": { "Org2": {
"mspid": "Org2MSP", "mspid": "Org2MSP",
"peers": [ "peers": [
"org2-peer1" "org2-peers"
], ],
"certificateAuthorities": [ "certificateAuthorities": [
"org2-ecert-ca" "org2-ca"
] ]
} }
}, },
"peers": { "peers": {
"org2-peer1": { "org2-peers": {
"url": "grpcs://org2-peer1:7051", "url": "grpcs://org2-peer-gateway-svc:7051",
"tlsCACerts": { "tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc6gAwIBAgIUJJ4wGOSCfw8XOOIx29o67wBpFB4wCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkyMDExNDEwMFoXDTM2MDkxNjExNDEwMFowaDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMtY2Etc2Vy\\ndmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyzGJLZX6pe59QAIBacjfzU4I\\nHezBYLyEu4ySpFx4xwxNLE4BWqLhB1VaOuenSQATM8pmSAy7i1830oM9elKWK6NW\\nMFQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\\nFEoAAhmjq/3M8CFPc7N8SL53erL5MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E\\nAwIDSAAwRQIhAJQ5PJOT4Gg8oiBU2KthMPkZqOLeu3Li4S3yBpLFgbsgAiB960P2\\nXPMu3HLoNXrktYOL9JzWlGyYRSPAnkap5Bsj0w==\\n-----END CERTIFICATE-----\\n" "pem": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc6gAwIBAgIUJJ4wGOSCfw8XOOIx29o67wBpFB4wCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkyMDExNDEwMFoXDTM2MDkxNjExNDEwMFowaDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMtY2Etc2Vy\\ndmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyzGJLZX6pe59QAIBacjfzU4I\\nHezBYLyEu4ySpFx4xwxNLE4BWqLhB1VaOuenSQATM8pmSAy7i1830oM9elKWK6NW\\nMFQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\\nFEoAAhmjq/3M8CFPc7N8SL53erL5MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E\\nAwIDSAAwRQIhAJQ5PJOT4Gg8oiBU2KthMPkZqOLeu3Li4S3yBpLFgbsgAiB960P2\\nXPMu3HLoNXrktYOL9JzWlGyYRSPAnkap5Bsj0w==\\n-----END CERTIFICATE-----\\n"
}, },
"grpcOptions": { "grpcOptions": {
"ssl-target-name-override": "org2-peer1", "ssl-target-name-override": "org2-peer-gateway-svc",
"hostnameOverride": "org2-peer1" "hostnameOverride": "org2-peer-gateway-svc"
} }
} }
}, },
"certificateAuthorities": { "certificateAuthorities": {
"org2-ecert-ca": { "org2-ca": {
"url": "https://org2-ecert-ca", "url": "https://org2-ca",
"caName": "org2-ecert-ca", "caName": "org2-ca",
"tlsCACerts": { "tlsCACerts": {
"pem": ["-----BEGIN CERTIFICATE-----\\nMIICKDCCAc6gAwIBAgIUJAF4fQK1KsnvdaUjau462D/5HPYwCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkxOTExMTcwMFoXDTM2MDkxNTExMTcwMFowaDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMtY2Etc2Vy\\ndmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8bLvzagP3YANMGHVomZoGCQD\\nRgM3SenagZQ4IWqNQJSV3yTxzdgAWnPhwc+B/HdAOvAq2Oz54FmiSL9dAJoivqNW\\nMFQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\\nFDdBAwT47jtbj48aXdMfRvMPbD5tMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E\\nAwIDSAAwRQIhAITSk4lYWqu12jZkR94aNoKT36ctaeKHuRvXs7m2qaHSAiAtUPO7\\nXlHtI9SDTRvI4DNSb2O7y7+B3WxVeCx50fivDw==\\n-----END CERTIFICATE-----\\n"] "pem": ["-----BEGIN CERTIFICATE-----\\nMIICKDCCAc6gAwIBAgIUJAF4fQK1KsnvdaUjau462D/5HPYwCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkxOTExMTcwMFoXDTM2MDkxNTExMTcwMFowaDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMtY2Etc2Vy\\ndmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8bLvzagP3YANMGHVomZoGCQD\\nRgM3SenagZQ4IWqNQJSV3yTxzdgAWnPhwc+B/HdAOvAq2Oz54FmiSL9dAJoivqNW\\nMFQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\\nFDdBAwT47jtbj48aXdMfRvMPbD5tMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E\\nAwIDSAAwRQIhAITSk4lYWqu12jZkR94aNoKT36ctaeKHuRvXs7m2qaHSAiAtUPO7\\nXlHtI9SDTRvI4DNSb2O7y7+B3WxVeCx50fivDw==\\n-----END CERTIFICATE-----\\n"]
}, },
@ -179,7 +179,7 @@ spec:
spec: spec:
containers: containers:
- name: main - name: main
image: ghcr.io/hyperledgendary/fabric-rest-sample image: ghcr.io/hyperledger/fabric-rest-sample
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: LOG_LEVEL - name: LOG_LEVEL
@ -236,23 +236,26 @@ spec:
selector: selector:
app: fabric-rest-sample app: fabric-rest-sample
--- ---
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
labels:
app: fabric-rest-sample
name: fabric-rest-sample name: fabric-rest-sample
# annotations:
# nginx.ingress.kubernetes.io/rewrite-target: /$1
spec: spec:
ingressClassName: nginx
rules: rules:
- http: - host: fabric-rest-sample.${DOMAIN}
http:
paths: paths:
# - path: "/fabric-rest-sample/(.*)" - backend:
- path: "/"
pathType: Prefix
backend:
service: service:
name: fabric-rest-sample name: fabric-rest-sample
port: port:
number: 3000 name: http
path: /
pathType: ImplementationSpecific

View file

@ -1,61 +0,0 @@
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: org0-admin-cli
spec:
replicas: 1
selector:
matchLabels:
app: org0-admin-cli
template:
metadata:
labels:
app: org0-admin-cli
spec:
containers:
- name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-tools:{{FABRIC_VERSION}}
imagePullPolicy: IfNotPresent
env:
- name: FABRIC_CFG_PATH
value: /var/hyperledger/fabric/config
args:
- sleep
- "2147483647"
workingDir: /root
volumeMounts:
- name: fabric-volume
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric/config
# This init container will unfurl all of the MSP archives listed in the msp-config config map.
initContainers:
- name: msp-unfurl
image: busybox
command:
- sh
- -c
- "for msp in $(ls /msp/msp-*.tgz); do echo $msp && tar zxvf $msp -C /var/hyperledger/fabric; done"
volumeMounts:
- name: msp-config
mountPath: /msp
- name: fabric-volume
mountPath: /var/hyperledger
volumes:
- name: fabric-volume
persistentVolumeClaim:
claimName: fabric-org0
- name: fabric-config
configMap:
name: org0-config
- name: msp-config
configMap:
name: msp-config

View file

@ -0,0 +1,124 @@
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: org0-ca-tls-cert
spec:
isCA: false
privateKey:
algorithm: ECDSA
size: 256
dnsNames:
- localhost
- org0-ca
- org0-ca.test-network.svc.cluster.local
- org0-ca.${DOMAIN}
ipAddresses:
- 127.0.0.1
secretName: org0-ca-tls-cert
issuerRef:
name: org0-tls-cert-issuer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: org0-ca
spec:
replicas: 1
selector:
matchLabels:
app: org0-ca
template:
metadata:
labels:
app: org0-ca
spec:
containers:
- name: main
image: ${FABRIC_CONTAINER_REGISTRY}/fabric-ca:${FABRIC_CA_VERSION}
imagePullPolicy: IfNotPresent
env:
- name: FABRIC_CA_SERVER_CA_NAME
value: "org0-ca"
- name: FABRIC_CA_SERVER_DEBUG
value: "false"
- name: FABRIC_CA_SERVER_HOME
value: "/var/hyperledger/fabric-ca-server"
- name: FABRIC_CA_SERVER_TLS_CERTFILE
value: "/var/hyperledger/fabric/config/tls/tls.crt"
- name: FABRIC_CA_SERVER_TLS_KEYFILE
value: "/var/hyperledger/fabric/config/tls/tls.key"
- name: FABRIC_CA_CLIENT_HOME
value: "/var/hyperledger/fabric-ca-client"
ports:
- containerPort: 443
volumeMounts:
- name: fabric-volume
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric-ca-server/fabric-ca-server-config.yaml
subPath: fabric-ca-server-config.yaml
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
readinessProbe:
tcpSocket:
port: 443
initialDelaySeconds: 2
periodSeconds: 5
volumes:
- name: fabric-volume
persistentVolumeClaim:
claimName: fabric-org0
- name: fabric-config
configMap:
name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-ca-tls-cert
---
apiVersion: v1
kind: Service
metadata:
name: org0-ca
spec:
ports:
- name: https
port: 443
protocol: TCP
selector:
app: org0-ca
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
labels:
app: org0-ca
name: org0-ca
spec:
ingressClassName: nginx
rules:
- host: org0-ca.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-ca
port:
name: https
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- org0-ca.${DOMAIN}

View file

@ -1,70 +0,0 @@
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: org0-ecert-ca
spec:
replicas: 1
selector:
matchLabels:
app: org0-ecert-ca
template:
metadata:
labels:
app: org0-ecert-ca
spec:
containers:
- name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-ca:{{FABRIC_CA_VERSION}}
imagePullPolicy: IfNotPresent
env:
- name: FABRIC_CA_SERVER_CA_NAME
value: "org0-ecert-ca"
- name: FABRIC_CA_SERVER_DEBUG
value: "false"
- name: FABRIC_CA_SERVER_HOME
value: "/var/hyperledger/fabric-ca-server"
- name: FABRIC_CA_SERVER_TLS_CERTFILE
value: "/var/hyperledger/fabric-ca-client/tls-ca/rcaadmin/msp/signcerts/cert.pem"
- name: FABRIC_CA_SERVER_TLS_KEYFILE
value: "/var/hyperledger/fabric-ca-client/tls-ca/rcaadmin/msp/keystore/key.pem"
- name: FABRIC_CA_CLIENT_HOME
value: "/var/hyperledger/fabric-ca-client"
ports:
- containerPort: 443
volumeMounts:
- name: fabric-volume
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric-ca-server/fabric-ca-server-config.yaml
subPath: fabric-ecert-ca-server-config.yaml
readinessProbe:
tcpSocket:
port: 443
initialDelaySeconds: 2
periodSeconds: 5
volumes:
- name: fabric-volume
persistentVolumeClaim:
claimName: fabric-org0
- name: fabric-config
configMap:
name: org0-config
---
apiVersion: v1
kind: Service
metadata:
name: org0-ecert-ca
spec:
ports:
- name: tls
port: 443
protocol: TCP
selector:
app: org0-ecert-ca

View file

@ -3,6 +3,30 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: org0-orderer1-tls-cert
namespace: ${NS}
spec:
isCA: false
privateKey:
algorithm: ECDSA
size: 256
dnsNames:
- localhost
- org0-orderer1
- org0-orderer1.${NS}.svc.cluster.local
- org0-orderer1.${DOMAIN}
- org0-orderer1-admin.${DOMAIN}
ipAddresses:
- 127.0.0.1
secretName: org0-orderer1-tls-cert
issuerRef:
name: org0-tls-cert-issuer
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP ORDERER_GENERAL_LOCALMSPID: OrdererMSP
ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp
ORDERER_GENERAL_TLS_ENABLED: "true" ORDERER_GENERAL_TLS_ENABLED: "true"
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/signcerts/cert.pem ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/cacerts/org0-tls-ca.pem ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/keystore/server.key ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
ORDERER_GENERAL_BOOTSTRAPMETHOD: none ORDERER_GENERAL_BOOTSTRAPMETHOD: none
ORDERER_ADMIN_TLS_ENABLED: "true"
ORDERER_ADMIN_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_ADMIN_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_ADMIN_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
# Authenticate client connections with the org's ecert / admin user enrollments
ORDERER_ADMIN_TLS_CLIENTROOTCAS: "[/var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/cacerts/org0-ca.pem]"
ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer1 ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer1
ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec: spec:
containers: containers:
- name: main - name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}} image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger mountPath: /var/hyperledger
- name: fabric-config - name: fabric-config
mountPath: /var/hyperledger/fabric/config mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes: volumes:
- name: fabric-volume - name: fabric-volume
persistentVolumeClaim: persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config - name: fabric-config
configMap: configMap:
name: org0-config name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer1-tls-cert
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -83,3 +118,42 @@ spec:
protocol: TCP protocol: TCP
selector: selector:
app: org0-orderer1 app: org0-orderer1
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
labels:
app: org0-orderer1
name: org0-orderer1
spec:
ingressClassName: nginx
rules:
- host: org0-orderer1.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer1
port:
name: general
path: /
pathType: ImplementationSpecific
- host: org0-orderer1-admin.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer1
port:
name: admin
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- org0-orderer1.${DOMAIN}
- hosts:
- org0-orderer1-admin.${DOMAIN}

View file

@ -3,6 +3,30 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: org0-orderer2-tls-cert
namespace: ${NS}
spec:
isCA: false
privateKey:
algorithm: ECDSA
size: 256
dnsNames:
- localhost
- org0-orderer2
- org0-orderer2.${NS}.svc.cluster.local
- org0-orderer2.${DOMAIN}
- org0-orderer2-admin.${DOMAIN}
ipAddresses:
- 127.0.0.1
secretName: org0-orderer2-tls-cert
issuerRef:
name: org0-tls-cert-issuer
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP ORDERER_GENERAL_LOCALMSPID: OrdererMSP
ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp
ORDERER_GENERAL_TLS_ENABLED: "true" ORDERER_GENERAL_TLS_ENABLED: "true"
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/signcerts/cert.pem ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/cacerts/org0-tls-ca.pem ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/keystore/server.key ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
ORDERER_GENERAL_BOOTSTRAPMETHOD: none ORDERER_GENERAL_BOOTSTRAPMETHOD: none
ORDERER_ADMIN_TLS_ENABLED: "true"
ORDERER_ADMIN_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_ADMIN_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_ADMIN_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
# Authenticate client connections with the org's ecert / admin user enrollments
ORDERER_ADMIN_TLS_CLIENTROOTCAS: "[/var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp/cacerts/org0-ca.pem]"
ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer2 ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer2
ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec: spec:
containers: containers:
- name: main - name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}} image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger mountPath: /var/hyperledger
- name: fabric-config - name: fabric-config
mountPath: /var/hyperledger/fabric/config mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes: volumes:
- name: fabric-volume - name: fabric-volume
persistentVolumeClaim: persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config - name: fabric-config
configMap: configMap:
name: org0-config name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer2-tls-cert
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -83,3 +118,42 @@ spec:
protocol: TCP protocol: TCP
selector: selector:
app: org0-orderer2 app: org0-orderer2
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
labels:
app: org0-orderer2
name: org0-orderer2
spec:
ingressClassName: nginx
rules:
- host: org0-orderer2.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer2
port:
name: general
path: /
pathType: ImplementationSpecific
- host: org0-orderer2-admin.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer2
port:
name: admin
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- org0-orderer2.${DOMAIN}
- hosts:
- org0-orderer2-admin.${DOMAIN}

View file

@ -3,6 +3,30 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: org0-orderer3-tls-cert
namespace: ${NS}
spec:
isCA: false
privateKey:
algorithm: ECDSA
size: 256
dnsNames:
- localhost
- org0-orderer3
- org0-orderer3.${NS}.svc.cluster.local
- org0-orderer3.${DOMAIN}
- org0-orderer3-admin.${DOMAIN}
ipAddresses:
- 127.0.0.1
secretName: org0-orderer3-tls-cert
issuerRef:
name: org0-tls-cert-issuer
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP ORDERER_GENERAL_LOCALMSPID: OrdererMSP
ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp ORDERER_GENERAL_LOCALMSPDIR: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp
ORDERER_GENERAL_TLS_ENABLED: "true" ORDERER_GENERAL_TLS_ENABLED: "true"
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/signcerts/cert.pem ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/cacerts/org0-tls-ca.pem ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/keystore/server.key ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
ORDERER_GENERAL_BOOTSTRAPMETHOD: none ORDERER_GENERAL_BOOTSTRAPMETHOD: none
ORDERER_ADMIN_TLS_ENABLED: "true"
ORDERER_ADMIN_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_ADMIN_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_ADMIN_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
# Authenticate client connections with the org's ecert / admin user enrollments
ORDERER_ADMIN_TLS_CLIENTROOTCAS: "[/var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp/cacerts/org0-ca.pem]"
ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer3 ORDERER_FILELEDGER_LOCATION: /var/hyperledger/fabric/data/orderer3
ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal ORDERER_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec: spec:
containers: containers:
- name: main - name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}} image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger mountPath: /var/hyperledger
- name: fabric-config - name: fabric-config
mountPath: /var/hyperledger/fabric/config mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes: volumes:
- name: fabric-volume - name: fabric-volume
persistentVolumeClaim: persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config - name: fabric-config
configMap: configMap:
name: org0-config name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer3-tls-cert
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -83,3 +118,42 @@ spec:
protocol: TCP protocol: TCP
selector: selector:
app: org0-orderer3 app: org0-orderer3
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
labels:
app: org0-orderer3
name: org0-orderer3
spec:
ingressClassName: nginx
rules:
- host: org0-orderer3.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer3
port:
name: general
path: /
pathType: ImplementationSpecific
- host: org0-orderer3-admin.${DOMAIN}
http:
paths:
- backend:
service:
name: org0-orderer3
port:
name: admin
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- org0-orderer3.${DOMAIN}
- hosts:
- org0-orderer3-admin.${DOMAIN}

Some files were not shown because too many files have changed in this diff Show more