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) |
| [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 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) |
| [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) |

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 { TextDecoder } from 'util';
const channelName = 'mychannel';
const chaincodeName = 'basic';
const mspId = 'Org1MSP';
const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
const mspId = envOrDefault('MSP_ID', 'Org1MSP');
// 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.
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.
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.
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.
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 assetId = `asset${Date.now()}`;
async function main(): Promise<void> {
await displayInputParameters();
// The gRPC client connection should be shared by all Gateway connections to this endpoint.
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> {
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',
'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);
}
}
/**
* 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 {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
dependencies {

View file

@ -1,6 +1,5 @@
# the first stage
FROM gradle:jdk11 AS GRADLE_BUILD
ARG CC_SERVER_PORT
# copy the build.gradle and src code to the container
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
FROM openjdk:11-jre
ARG CC_SERVER_PORT=9999
# Setup tini to work better handle signals
ENV TINI_VERSION v0.19.0

View file

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

View file

@ -138,7 +138,7 @@ class AssetTransfer extends Contract {
const oldOwner = asset.Owner;
asset.Owner = newOwner;
// 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;
}

View file

@ -2,7 +2,6 @@
# SPDX-License-Identifier: Apache-2.0
#
FROM node:16 AS builder
ARG CC_SERVER_PORT
WORKDIR /usr/src/app
@ -12,6 +11,7 @@ RUN npm ci && npm run package
FROM node:16 AS production
ARG CC_SERVER_PORT
# Setup tini to work better handle signals
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).
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
@ -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)
```shell
export REDIS_PASSWORD=$(uuidgen)
npm run start:redis
```
@ -132,13 +133,9 @@ npm run start:dev
### 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
```shell
docker build -t fabric-rest-sample .
```
Clone the `fabric-samples` repository and change to the `fabric-samples/asset-transfer-basic/rest-api-typescript` directory before running the following commands
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
```shell
export REDIS_PASSWORD=$(uuidgen)
docker-compose up -d
```
@ -187,7 +185,7 @@ curl --include --header "X-Api-Key: ${SAMPLE_APIKEY}" --request OPTIONS http://l
### Create an asset...
```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
@ -239,13 +237,13 @@ You should see the newly created asset, for example
### Update an asset...
```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...
```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...

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ export const ORG2 = 'Org2';
export const JOB_QUEUE_NAME = 'submit';
/*
/**
* Log level for the REST server
*/
export const logLevel = env
@ -28,7 +28,7 @@ export const logLevel = env
.default('info')
.asEnum(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']);
/*
/**
* The port to start the REST server on
*/
export const port = env
@ -37,7 +37,7 @@ export const port = env
.example('3000')
.asPortNumber();
/*
/**
* The type of backoff to use for retrying failed submit jobs
*/
export const submitJobBackoffType = env
@ -45,7 +45,7 @@ export const submitJobBackoffType = env
.default('fixed')
.asEnum(['fixed', 'exponential']);
/*
/**
* Backoff delay for retrying failed submit jobs in milliseconds
*/
export const submitJobBackoffDelay = env
@ -54,7 +54,7 @@ export const submitJobBackoffDelay = env
.example('3000')
.asIntPositive();
/*
/**
* The total number of attempts to try a submit job until it completes
*/
export const submitJobAttempts = env
@ -63,7 +63,7 @@ export const submitJobAttempts = env
.example('5')
.asIntPositive();
/*
/**
* The maximum number of submit jobs that can be processed in parallel
*/
export const submitJobConcurrency = env
@ -72,7 +72,7 @@ export const submitJobConcurrency = env
.example('5')
.asIntPositive();
/*
/**
* The number of completed submit jobs to keep
*/
export const maxCompletedSubmitJobs = env
@ -81,7 +81,7 @@ export const maxCompletedSubmitJobs = env
.example('1000')
.asIntPositive();
/*
/**
* The number of failed submit jobs to keep
*/
export const maxFailedSubmitJobs = env
@ -90,7 +90,7 @@ export const maxFailedSubmitJobs = env
.example('1000')
.asIntPositive();
/*
/**
* 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
* more than one for redundancy
@ -101,7 +101,7 @@ export const submitJobQueueScheduler = env
.example('true')
.asBoolStrict();
/*
/**
* Whether to convert discovered host addresses to be 'localhost'
* 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'
@ -112,7 +112,7 @@ export const asLocalhost = env
.example('true')
.asBoolStrict();
/*
/**
* The Org1 MSP ID
*/
export const mspIdOrg1 = env
@ -121,7 +121,7 @@ export const mspIdOrg1 = env
.example(`${ORG1}MSP`)
.asString();
/*
/**
* The Org2 MSP ID
*/
export const mspIdOrg2 = env
@ -130,7 +130,7 @@ export const mspIdOrg2 = env
.example(`${ORG2}MSP`)
.asString();
/*
/**
* Name of the channel which the basic asset sample chaincode has been installed on
*/
export const channelName = env
@ -139,7 +139,7 @@ export const channelName = env
.example('mychannel')
.asString();
/*
/**
* Name used to install the basic asset sample
*/
export const chaincodeName = env
@ -148,7 +148,7 @@ export const chaincodeName = env
.example('basic')
.asString();
/*
/**
* The transaction submit timeout in seconds for commit notification to complete
*/
export const commitTimeout = env
@ -157,7 +157,7 @@ export const commitTimeout = env
.example('300')
.asIntPositive();
/*
/**
* The transaction submit timeout in seconds for the endorsement to complete
*/
export const endorseTimeout = env
@ -166,7 +166,7 @@ export const endorseTimeout = env
.example('30')
.asIntPositive();
/*
/**
* The transaction query timeout in seconds
*/
export const queryTimeout = env
@ -175,7 +175,7 @@ export const queryTimeout = env
.example('3')
.asIntPositive();
/*
/**
* The Org1 connection profile JSON
*/
export const connectionProfileOrg1 = env
@ -186,7 +186,7 @@ export const connectionProfileOrg1 = env
)
.asJsonObject() as Record<string, unknown>;
/*
/**
* Certificate for an Org1 identity to evaluate and submit transactions
*/
export const certificateOrg1 = env
@ -195,7 +195,7 @@ export const certificateOrg1 = env
.example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"')
.asString();
/*
/**
* Private key for an Org1 identity to evaluate and submit transactions
*/
export const privateKeyOrg1 = env
@ -204,7 +204,7 @@ export const privateKeyOrg1 = env
.example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"')
.asString();
/*
/**
* The Org2 connection profile JSON
*/
export const connectionProfileOrg2 = env
@ -215,7 +215,7 @@ export const connectionProfileOrg2 = env
)
.asJsonObject() as Record<string, unknown>;
/*
/**
* Certificate for an Org2 identity to evaluate and submit transactions
*/
export const certificateOrg2 = env
@ -224,7 +224,7 @@ export const certificateOrg2 = env
.example('"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n"')
.asString();
/*
/**
* Private key for an Org2 identity to evaluate and submit transactions
*/
export const privateKeyOrg2 = env
@ -233,7 +233,7 @@ export const privateKeyOrg2 = env
.example('"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"')
.asString();
/*
/**
* The host the Redis server is running on
*/
export const redisHost = env
@ -242,7 +242,7 @@ export const redisHost = env
.example('localhost')
.asString();
/*
/**
* The port the Redis server is running on
*/
export const redisPort = env
@ -251,7 +251,7 @@ export const redisPort = env
.example('6379')
.asPortNumber();
/*
/**
* Username for the Redis server
*/
export const redisUsername = env
@ -259,12 +259,12 @@ export const redisUsername = env
.example('fabric')
.asString();
/*
/**
* Password for the Redis server
*/
export const redisPassword = env.get('REDIS_PASSWORD').asString();
/*
/**
* API key for Org1
* 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')
.asString();
/*
/**
* API key for Org2
* 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 { logger } from './logger';
/*
/**
* Base type for errors from the smart contract.
*
* 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
* 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
* 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
* implementation when an asset does not exist.
*/
@ -64,26 +64,30 @@ export class AssetNotFoundError extends ContractError {
}
}
/*
/**
* 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 {
/**
* Transactions should be retried using the same transaction ID to protect
* against duplicate transactions being committed if a timeout error occurs
*/
WithExistingTransactionId,
/**
* Transactions which could not be committed due to other errors require a
* new transaction ID when retrying
*/
WithNewTransactionId,
/**
* Transactions that failed due to a duplicate transaction error, or errors
* from the smart contract, should not be retried
*/
None,
}
/*
/**
* Get the required transaction retry action for an error.
*
* 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
* instead, for example:
* MVCC_READ_CONFLICT
* PHANTOM_READ_CONFLICT
* ENDORSEMENT_POLICY_FAILURE
* CHAINCODE_VERSION_CONFLICT
* EXPIRED_CHAINCODE
* - MVCC_READ_CONFLICT
* - PHANTOM_READ_CONFLICT
* - ENDORSEMENT_POLICY_FAILURE
* - CHAINCODE_VERSION_CONFLICT
* - EXPIRED_CHAINCODE
*/
export const getRetryAction = (err: unknown): RetryAction => {
if (isDuplicateTransactionError(err) || err instanceof ContractError) {
@ -108,7 +112,7 @@ export const getRetryAction = (err: unknown): RetryAction => {
return RetryAction.WithNewTransactionId;
};
/*
/**
* Type guard to make catching unknown errors easier
*/
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.
*
* This is ...painful.
@ -155,13 +159,13 @@ export const isDuplicateTransactionError = (err: unknown): boolean => {
return isDuplicate === true;
};
/*
/**
* Matches asset already exists error strings from the asset contract
*
* The regex needs to match the following error messages:
* "the asset %s already exists"
* "The asset ${id} already exists"
* "Asset %s already exists"
* - "the asset %s already exists"
* - "The asset ${id} already exists"
* - "Asset %s already exists"
*/
const matchAssetAlreadyExistsMessage = (message: string): string | null => {
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
@ -178,13 +182,13 @@ const matchAssetAlreadyExistsMessage = (message: string): string | null => {
return null;
};
/*
/**
* Matches asset does not exist error strings from the asset contract
*
* The regex needs to match the following error messages:
* "the asset %s does not exist"
* "The asset ${id} does not exist"
* "Asset %s does not exist"
* - "the asset %s does not exist"
* - "The asset ${id} does not exist"
* - "Asset %s does not exist"
*/
const matchAssetDoesNotExistMessage = (message: string): string | null => {
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
@ -201,12 +205,12 @@ const matchAssetDoesNotExistMessage = (message: string): string | null => {
return null;
};
/*
/**
* Matches transaction does not exist error strings from the contract API
*
* 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 no such transaction ID [%s] 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"
*/
const matchTransactionDoesNotExistMessage = (
message: string
@ -228,11 +232,12 @@ const matchTransactionDoesNotExistMessage = (
return null;
};
/*
/**
* 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.
*
* Note: the error message text is not the same for the Go, Java, and
* Javascript implementations of the chaincode!
*/

View file

@ -18,7 +18,7 @@ import { logger } from './logger';
import { handleError } from './errors';
import * as protos from 'fabric-protos';
/*
/**
* 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
@ -55,7 +55,7 @@ export const createWallet = async (): Promise<Wallet> => {
return wallet;
};
/*
/**
* Create a Gateway connection
*
* Gateway instances can and should be reused rather than connecting to submit every transaction
@ -89,7 +89,7 @@ export const createGateway = async (
return gateway;
};
/*
/**
* 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
@ -100,7 +100,7 @@ export const getNetwork = async (gateway: Gateway): Promise<Network> => {
return network;
};
/*
/**
* Get the asset transfer sample contract and the qscc system contract
*
* The system contract is used for the liveness REST endpoint
@ -113,7 +113,7 @@ export const getContracts = async (
return { assetContract, qsccContract };
};
/*
/**
* Evaluate a transaction and handle any errors
*/
export const evatuateTransaction = async (
@ -137,7 +137,7 @@ export const evatuateTransaction = async (
}
};
/*
/**
* Submit a transaction and handle any errors
*/
export const submitTransaction = async (
@ -159,7 +159,7 @@ export const submitTransaction = async (
}
};
/*
/**
* Get the validation code of the specified transaction
*/
export const getTransactionValidationCode = async (
@ -181,7 +181,7 @@ export const getTransactionValidationCode = async (
return validationCode;
};
/*
/**
* Get the current block height
*
* 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,
};
/*
/**
* Set up the queue for submit jobs
*/
export const initJobQueue = (): Queue => {
@ -72,7 +72,7 @@ export const initJobQueue = (): Queue => {
return submitQueue;
};
/*
/**
* Set up a worker to process submit jobs on the queue, using the
* processSubmitTransactionJob function below
*/
@ -104,7 +104,7 @@ export const initJobQueueWorker = (app: Application): Worker => {
return worker;
};
/*
/**
* Process a submit transaction request from the job queue
*
* 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
*
* This manages stalled and delayed jobs and is required for retries with backoff
@ -226,7 +226,7 @@ export const initJobQueueScheduler = (): QueueScheduler => {
return queueScheduler;
};
/*
/**
* Helper to add a new submit transaction job to the queue
*/
export const addSubmitTransactionJob = async (
@ -250,7 +250,7 @@ export const addSubmitTransactionJob = async (
return job.id;
};
/*
/**
* Helper to update the data for an existing job
*/
export const updateJobData = async (
@ -274,7 +274,7 @@ export const updateJobData = async (
await job.update(newData);
};
/*
/**
* Gets a job summary
*
* This function is used for the jobs REST endpoint
@ -325,7 +325,7 @@ export const getJobSummary = async (
return jobSummary;
};
/*
/**
* Get the current job counts
*
* 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 { logger } from './logger';
/*
/**
* Check whether the maxmemory-policy config is set to noeviction
*
* 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> => {
const app = express();
// Remember for production usage, to check any TLS or CORS requirements
app.use(
pinoMiddleware({
logger,

View file

@ -1,91 +1,81 @@
# Asset Transfer Events Sample
# Asset transfer events sample
The asset transfer events sample demonstrates chaincode events send/receive
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
transaction is committed to the ledger. The block events are published when
a block is committed to the ledger, containing all the transaction details
within that block.
The asset transfer events sample demonstrates:
- Emitting chaincode events from smart contract transaction functions.
- Receiving chaincode events in a client application.
- Replaying previous chaincode events in a client application.
Events are published when a block is committed to the ledger.
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)
page in the Fabric documentation.
## About the Sample
## About the sample
This sample includes chaincodes and application code in multiple languages.
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.
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.
### 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
- 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.
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**").
- 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.
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.
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.
### 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
- 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 the use of sending and receiving events.
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.
## Running the sample
Like other samples, we will use the Fabric test network to deploy and run ths sample. Follow these step in order.
- Create the test network and a channel
```
cd test-network
./network.sh up createChannel -c mychannel -ca
```
Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
- Deploy the chaincode (smart contract)
```
# to deploy javascript version
./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
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
# or to deploy java version
./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
```
1. Deploy one of the smart contract implementations (from the `test-network` folder).
```
# 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
```
cd application-javascript
npm install
# ensure this line in app.js have correct chaincode deploy name
# const chaincodeName = '...';
node app.js
```
# 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')"
```
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
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
```
```

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
// to deploy a chaincode to a channel.
// ===> 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
// ===> from directory asset-transfer-sbe/application-javascript
// ===> from directory asset-transfer-events/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory asset-transfer-sbe/application-javascript
// ===> from directory asset-transfer-events/application-javascript
// npm install
// - to run this test application
// ===> from directory asset-transfer-sbe/application-javascript
// ===> from directory asset-transfer-events/application-javascript
// node app.js
// NOTE: If you see an error like these:

View file

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

View file

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

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Running TestApp:
// gradle runApp
// Running TestApp:
// gradle runApp
package application.java;
@ -128,7 +128,7 @@ public class App {
result = contract.evaluateTransaction("QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Jin Soo\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}");
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("Evaluate Transaction:GetAssetsByRangeWithPagination assets 3-5");
result = contract.evaluateTransaction("GetAssetsByRangeWithPagination", "asset3", "asset6", "3","");

View file

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

View file

@ -99,7 +99,7 @@ type Asset struct {
// HistoryQueryResult structure used for returning result of history query
type HistoryQueryResult struct {
Record *Asset `json:"record"`
TxId string `json:"txId"`
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
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.
// Paginated range queries are only valid for read only transactions.
// 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 {
return nil, err
}
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

View file

@ -266,10 +266,9 @@ class Chaincode extends Contract {
results.results = await this._GetAllResults(iterator, false);
results.ResponseMetadata = {
RecordsCount: metadata.fetchedRecordsCount,
Bookmark: metadata.bookmark,
};
results.fetchedRecordsCount = metadata.fetchedRecordsCount;
results.bookmark = metadata.bookmark;
return JSON.stringify(results);
}
@ -289,10 +288,9 @@ class Chaincode extends Contract {
results.results = await this._GetAllResults(iterator, false);
results.ResponseMetadata = {
RecordsCount: metadata.fetchedRecordsCount,
Bookmark: metadata.bookmark,
};
results.fetchedRecordsCount = metadata.fetchedRecordsCount;
results.bookmark = metadata.bookmark;
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 {
maven {
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
}
jcenter()
mavenCentral()
maven {
url 'https://jitpack.io'
}

View file

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

View file

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

View file

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

View file

@ -8,13 +8,26 @@ trigger:
- release-2.2
variables:
FABRIC_VERSION: 2.4
GO_BIN: $(Build.Repository.LocalPath)/bin
GO_VER: 1.16.7
NODE_VER: 16.x
PATH: $(Build.Repository.LocalPath)/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
- name: FABRIC_VERSION
value: 2.4
- name: GO_BIN
value: $(Build.Repository.LocalPath)/bin
- 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:
- 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
displayName: Commercial Paper (Go)
pool:
@ -85,6 +98,8 @@ jobs:
inputs:
versionSpec: $(NODE_VER)
displayName: Install Node.js
- script: ./ci/scripts/shellcheck.sh
displayName: Lint Shell Scripts
- script: ./ci/scripts/lint.sh
displayName: Lint Code
@ -114,6 +129,20 @@ jobs:
workingDirectory: test-network
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
displayName: Test Network
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() {
print "Creating network"
print "Creating 3 Org network"
./network.sh up createChannel -ca -s couchdb
cd addOrg3
./addOrg3.sh up -ca -s couchdb
cd ..
print "Deploying ${CHAINCODE_NAME} chaincode"
./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
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
createNetwork
print "Initializing Javascript application"

View file

@ -34,3 +34,34 @@ popd
stopNetwork
print "Remove wallet storage"
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
popd
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
repositories {
mavenLocal()
mavenCentral()
maven {
url 'https://jitpack.io'

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ Invoke and query chaincode:
./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
```
@ -71,8 +71,8 @@ Tear down the cluster:
## 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)
- [ ] 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
- [ ] Implement mutual TLS across peers, orderers, and clients.
- [ ] Caliper?

View file

@ -25,7 +25,7 @@ Organizations:
ID: OrdererMSP
# 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
# For organization policies, their canonical path is usually
@ -54,7 +54,7 @@ Organizations:
# ID to load the MSP definition as
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
# For organization policies, their canonical path is usually
@ -89,7 +89,7 @@ Organizations:
# ID to load the MSP definition as
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
# For organization policies, their canonical path is usually
@ -224,16 +224,16 @@ Orderer: &OrdererDefaults
Consenters:
- Host: org0-orderer1
Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/signcerts/cert.pem
ServerTLSCert: /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: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem
- Host: org0-orderer2
Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/signcerts/cert.pem
ServerTLSCert: /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: ../../build/channel-msp/ordererOrganizations/org0/orderers/org0-orderer2/tls/signcerts/tls-cert.pem
- Host: org0-orderer3
Port: 6050
ClientTLSCert: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/signcerts/cert.pem
ServerTLSCert: /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: ../../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

View file

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

View file

@ -19,53 +19,54 @@ $ ./network up
Launching network "test-network":
...
✅ - Launching TLS CAs ...
✅ - Enrolling bootstrap TLS CA users ...
✅ - Registering and enrolling ECert CA bootstrap users ...
✅ - Initializing TLS certificate Issuers ...
✅ - Launching ECert CAs ...
✅ - Enrolling bootstrap ECert CA users ...
...
🏁 - Network is ready.
```
## [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
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
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.
- Each organization maintains two distinct, separate CA instances : one dedicated to [TLS](../kube/org0/org0-tls-ca.yaml)
Certificate Signing Requests, and a second process dedicated to [ECert](../kube/org0/org0-ecert-ca.yaml) Enrollments
and identity MSPs.
- 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.
- This guide simplifies the storage and organization of Fabric certificates into two distinct flows. For securing
inter-node communication with TLS, [cert-manager](https://cert-manager.io) is responsible for the lifecycle of issuing,
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.
- The `cryptogen` anti-pattern is **strictly forbidden**. All TLS and MSP enrollments are constructed using the CA
registration and enrollment REST services, coordinated by calls to `fabric-ca-client` running directly on the
CA pods. When working with certificates, the fabric CA client ONLY has visibility to the organization's local volume
storage.
- 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.
- TLS CA configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-tls-ca-server`
- 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`. At runtime, the ca-client
ONLY has visibility to the organization's shared volume mount.
- ECert CA configuration and certificates are maintained in each org's persistent volume at `/var/hyperledger/fabric-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.
- 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`
- 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.
- **_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
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
@ -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)
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. [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 TLS CA Issuers](#deploy-tls-ca-issuers)
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. [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)
## [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)
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
```
✅ - Initializing TLS certificate Issuers ...
...
```
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
map and persistent volume maps to the correct location on disk.
```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.
Each organization's CA `Issuer` will be used to construct a TLS `Certificate` for each node in the network. At
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`.
## [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.
### [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)
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
- `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
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)
```shell
@ -240,9 +160,9 @@ reference the TLS certificate authority and signing keys as generated by the adm
```
```shell
kubectl -n test-network apply -f kube/org0/org0-ecert-ca.yaml
kubectl -n test-network apply -f kube/org1/org1-ecert-ca.yaml
kubectl -n test-network apply -f kube/org2/org2-ecert-ca.yaml
kubectl -n test-network apply -f kube/org0/org0-ca.yaml
kubectl -n test-network apply -f kube/org1/org1-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.
@ -259,7 +179,7 @@ local MSP certificate structure for all of the nodes in our test network.
```shell
fabric-ca-client enroll \
--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
```
@ -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:
- One TLS CA `Service`, forwarding internal traffic from https://orgN-tls-ca to the TLS CA
- One TLS CA `Deployment`
- One TLS CA `Pod`
- One TLS CA `Issuer` and issuer `Certificate`
- One ECert CA `Service`, forwarding internal traffic from https://orgN-ecert-ca to the ECert CA
- One ECert CA `Deployment`
- 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.

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:
```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/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/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/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-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-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 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
```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

View file

@ -1,11 +1,11 @@
# Kubernetes
# Kubernetes
To get started with the Kube test network, you will need access to a Kubernetes cluster.
## TL/DR :
## TL/DR :
```shell
$ ./network kind
$ ./network kind
Initializing KIND cluster "kind":
✅ - Pulling docker images for Fabric 2.3.2 ...
✅ - Creating cluster "kind" ...
@ -16,43 +16,43 @@ Initializing KIND cluster "kind":
and :
```shell
$ ./network unkind
$ ./network unkind
Deleting cluster "kind":
☠️ - Deleting KIND cluster kind ...
🏁 - Cluster is gone.
```
## Kube Context:
## Kube Context:
For illustration purposes, this project attempts in all cases to _keep it simple_ as the
general rule. By default, we will rely on `kind` ([Kubernetes IN Docker](https://kind.sigs.k8s.io))
as a mechanism to quickly spin up ephemeral, short-lived clusters for development and
as a mechanism to quickly spin up ephemeral, short-lived clusters for development and
illustration.
To maximize portability across revisions, vendor distributions, hardware profiles, and
network topologies, this project relies _exclusively_ on scripted interaction with the
Kube API controller to reflect updates in a remote cluster. While this may not be the
ideal technique for managing production workloads, the objective of this guide is to provide
clarity on the nuances of Fabric / Kubernetes deployments, rather than an opinionated
perspective on state of the art techniques for cloud Dev/Ops. Targeting
the core Kube APIs means that there is a good chance that the systems will work "as-is"
simply by setting the kubectl context to reference a cloud-native cluster (e.g. OCP, IKS,
To maximize portability across revisions, vendor distributions, hardware profiles, and
network topologies, this project relies _exclusively_ on scripted interaction with the
Kube API controller to reflect updates in a remote cluster. While this may not be the
ideal technique for managing production workloads, the objective of this guide is to provide
clarity on the nuances of Fabric / Kubernetes deployments, rather than an opinionated
perspective on state of the art techniques for cloud Dev/Ops. Targeting
the core Kube APIs means that there is a good chance that the systems will work "as-is"
simply by setting the kubectl context to reference a cloud-native cluster (e.g. OCP, IKS,
AWS, etc.)
If you don't have access to an existing cluster, or want to set up a short-lived cluster
If you don't have access to an existing cluster, or want to set up a short-lived cluster
for development, testing, or CI, you can create a new cluster with:
```shell
$ ./network kind
```
or:
or:
```shell
$ kind create cluster
$ kind create cluster
```
By default, `kind` will set the current Kube context to reference the new cluster. Any
interaction with `kubectl` (or kube-context aware SDKs) will inherit the current context.
By default, `kind` will set the current Kube context to reference the new cluster. Any
interaction with `kubectl` (or kube-context aware SDKs) will inherit the current context.
```shell
$ kubectl cluster-info
@ -64,41 +64,41 @@ To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
When you are done with the cluster, tear it down with:
```shell
$ ./network unkind
$ ./network unkind
```
or:
or:
```shell
$ kind delete cluster
$ kind delete cluster
```
## Test Network Structure
To emulate a more realistic example of multi-party collaboration, the test network
forms a blockchain consensus group spanning three virtual organizations. Access to the
blockchain is entirely constrained to Kubernetes private networks, and consuming applications
To emulate a more realistic example of multi-party collaboration, the test network
forms a blockchain consensus group spanning three virtual organizations. Access to the
blockchain is entirely constrained to Kubernetes private networks, and consuming applications
make use of a Kube ingress controller for external visibility.
In k8s terms:
In k8s terms:
- The blockchain is contained within a single Kubernetes `Cluster`.
- The blockchain is contained within a single Kubernetes `Cluster`.
- Blockchain services (nodes, orderers, chaincode, etc.) reside within a single `Namespace`.
- Each organization maintains a distinct, independent `PersistentVolumeClaim` for TLS certificates,
local MSP, private data, and transaction ledgers.
- Smart Contracts rely exclusively on the [Chaincode-as-a-Service](link) and [External Builder](link)
patterns, running in the cluster as Kube `Deployments` with companion `Services`.
- An HTTP(s) `Ingress` and companion gateway application is required for external access to the blockchain.
patterns, running in the cluster as Kube `Deployments` with companion `Services`.
- An HTTP(s) `Ingress` and companion gateway application is required for external access to the blockchain.
When running the test network locally, the `./network kind` bootstrap will configure the system with
an [Nginx ingress controller](link), a private [Container Registry](link), and persistent volumes / claims for
host-local organization storage.
When running the test network locally, the `./network kind` bootstrap will configure the system with
an [Nginx ingress controller](link), a private [Container Registry](link), and persistent volumes / claims for
host-local organization storage.
Behind the scenes, `./network kind` is running:
Behind the scenes, `./network kind` is running:
```shell
# Create the KIND cluster and nginx ingress controller bound to :80 and :443
# Create the KIND cluster and nginx ingress controller bound to :80 and :443
kind create cluster --name ${TEST_NETWORK_CLUSTER_NAME:-kind} --config scripts/kind-config.yaml
# Create the Kube namespace
# Create the Kube namespace
kubectl create namespace ${TEST_NETWORK_NAMESPACE:-test-network}
# Create host persistent volumes (tied the kind-control-plane docker image lifetime)
@ -106,93 +106,92 @@ kubectl create -f kube/pv-fabric-org0.yaml
kubectl create -f kube/pv-fabric-org1.yaml
kubectl create -f kube/pv-fabric-org2.yaml
# Create persistent volume claims binding to the host (docker) volumes
kubectl -n $NS create -f kube/pvc-fabric-org0.yaml
kubectl -n $NS create -f kube/pvc-fabric-org1.yaml
kubectl -n $NS create -f kube/pvc-fabric-org2.yaml
# Create persistent volume claims binding to the host (docker) volumes
kubectl -n $NS create -f kube/pvc-fabric-org0.yaml
kubectl -n $NS create -f kube/pvc-fabric-org1.yaml
kubectl -n $NS create -f kube/pvc-fabric-org2.yaml
```
## Container Registry
The [kube yaml descriptors](../kube) generally rely on the public Fabric images maintained at the public
Docker and GitHub container registries. For casual usage, the test network will bootstrap and launch CAs,
The [kube yaml descriptors](../kube) generally rely on the public Fabric images maintained at the public
Docker and GitHub container registries. For casual usage, the test network will bootstrap and launch CAs,
peers, orderers, chaincode, and sample applications without any additional configuration.
While public images are made available for pre-canned samples, there will undoubtedly be cases
where you would like to build custom chaincode, gateway client applications, or custom builds of core
Fabric binaries without uploading your code to a public registry. For this purpose, the Kube test
network includes a [Local Registry](https://kind.sigs.k8s.io/docs/user/local-registry/) available for
you to _quickly_ deploy custom images directly into the cluster without uploading your code to the
While public images are made available for pre-canned samples, there will undoubtedly be cases
where you would like to build custom chaincode, gateway client applications, or custom builds of core
Fabric binaries without uploading your code to a public registry. For this purpose, the Kube test
network includes a [Local Registry](https://kind.sigs.k8s.io/docs/user/local-registry/) available for
you to _quickly_ deploy custom images directly into the cluster without uploading your code to the
Internet.
By default, the [kind.sh](../scripts/kind.sh) bootstrap will configure and link up a local container
registry running at `localhost:5000/`. Images pushed to this registry will be immediately available
By default, the [kind.sh](../scripts/kind.sh) bootstrap will configure and link up a local container
registry running at `localhost:5000/`. Images pushed to this registry will be immediately available
to Pods deployed to the local cluster.
For dev/test/CI based flows using an external registry, the traditional Kubernetes practice of
[Adding ImagePullSecrets to a service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account)
For dev/test/CI based flows using an external registry, the traditional Kubernetes practice of
[Adding ImagePullSecrets to a service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account)
still applies.
In some environments, KIND may encounter issues loading the Fabric docker images from the public container
registries. In addition, for Fabric development it can be advantageous to work with Docker images built
In some environments, KIND may encounter issues loading the Fabric docker images from the public container
registries. In addition, for Fabric development it can be advantageous to work with Docker images built
locally, bypassing the public images entirely. For these scenarios, images may also be [directly loaded](https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster)
into the KIND image plane, bypassing the container registry.
The `./network` script supports these additional modes via:
The `./network` script supports these additional modes via:
1. For network-constrained environments, pull all images to the local docker cache and load to KIND:
1. For network-constrained environments, pull all images to the local docker cache and load to KIND:
```shell
export TEST_NETWORK_STAGE_DOCKER_IMAGES=true
export TEST_NETWORK_STAGE_DOCKER_IMAGES=true
./network kind
./network kind
./network up
```
2. For alternate registries (e.g. local or Fabric CI/CD builds):
2. For alternate registries (e.g. local or Fabric CI/CD builds):
```shell
./network kind
./network kind
export TEST_NETWORK_FABRIC_CONTAINER_REGISTRY=hyperledger-fabric.jfrog.io
export TEST_NETWORK_FABRIC_VERSION=amd64-latest
export TEST_NETWORK_FABRIC_VERSION=amd64-latest
export TEST_NETWORK_FABRIC_CA_VERSION=amd64-latest
./network up
```
3. For working with Fabric images built locally:
3. For working with Fabric images built locally:
```shell
./network kind
./network kind
make docker # in hyperledger/fabric
make docker # in hyperledger/fabric
export TEST_NETWORK_FABRIC_VERSION=2.4.0
./network load-images
./network up
./network load-images
./network up
```
## Cloud Vendors
## Cloud Vendors
While the test network primarily targets KIND clusters, the singular reliance on the Kube API plane
means that it should also work without modification on any modern cloud-based or bare metal
Kubernetes distribution. While supporting the entire ecosystem of cloud vendors is not in scope
for this sample project, we'd love to hear feedback, success stories, or bugs related to applying the
While the test network primarily targets KIND clusters, the singular reliance on the Kube API plane
means that it should also work without modification on any modern cloud-based or bare metal
Kubernetes distribution. While supporting the entire ecosystem of cloud vendors is not in scope
for this sample project, we'd love to hear feedback, success stories, or bugs related to applying the
test network to additional platforms.
In general, at a high-level the steps required to port the test network to ANY kube vendor are:
In general, at a high-level the steps required to port the test network to ANY kube vendor are:
- Configure an HTTP `Ingress` for access to any gateway, REST, or companion blockchain applications.
- Register `PersistentVolumeClaims` for each of the organizations in the test network.
- Register `PersistentVolumeClaims` for each of the organizations in the test network.
- Create a `Namespace` for each instance of the test network.
- Upload your chaincode, gateway clients, and application logic to an external Container Registry.
- Run with a `ServiceAccount` and role bindings suitable for creating `Pods`, `Deployments`, and `Services`.
- Upload your chaincode, gateway clients, and application logic to an external Container Registry.
- Run with a `ServiceAccount` and role bindings suitable for creating `Pods`, `Deployments`, and `Services`.
Example configurations for common cloud vendors:
Example configurations for common cloud vendors:
### IKS
### OCP
### AWS
### Azure
### IKS
### OCP
### AWS
### Azure
## 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
simple blockchain applications.
While test-network provided a solid foundation for casual Fabric development, the over-reliance on
[Docker Compose](https://docs.docker.com/compose/) introduced tremendous, non-trivial complexity when transitioning
applications to production. Without belaboring the many issues and anti-patterns present in the Compose-based
test network, we'll submit that the best path forward is to _align_ the development and production patterns around a
common orchestration framework - Kubernetes.
As a supplement to the docker-compose based test-network, this guide presents an equivalent Fabric network
suitable for running sample applications and chaincode, developing Gateway and Chaincode-as-a-Service applications,
and harmonizing CI and deployment flows with a unified container framework - Kubernetes.
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)
@ -25,8 +23,7 @@ _Ahoy!_
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
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
and ECert CAs for management of local, channel, and user MSP contexts.
_Chaincode-as-a-Service_ running in a shared Kubernetes namespace.
![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)
- [Certificate Authorities](CA.md)
- [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)
- [Launching the Test Network](TEST_NETWORK.md)
- [Registering and Enrolling Identities](CA.md#registering-and-enrolling-identities)

View file

@ -1,87 +1,73 @@
## Network Overview
After we have set up a series of TLS and ECert CA services, we'll use the CAs to generate
[Local MSP](https://hyperledger-fabric.readthedocs.io/en/latest/membership/membership.html#local-msps) structures for
After we have set up a series of TLS and ECert CA services, we'll use the CAs to generate
[Local MSP](https://hyperledger-fabric.readthedocs.io/en/latest/membership/membership.html#local-msps) structures for
all of the nodes, using the local MSPs to launch our network peers and orderers.
### TL/DR :
### TL/DR :
```shell
./network up
...
./network up
...
✅ - Creating local node MSP ...
✅ - Launching orderers ...
✅ - Launching peers ...
🏁 - Network is ready.
```
## Fabric MSP Context
## Fabric MSP Context
Before we launch the network peers and orderers, each node in the network needs to have available:
Before we launch the network peers and orderers, each node in the network needs to have available:
- TLS Root Certificates for all organizations in the network
- TLS Certificates and Signing Keys for SSL server/hostname verification of the network node
- Enrollment Certificates validating the network node identity (local MSP)
- Enrollment Certificates for an `Admin` identity / role for the organization.
- Enrollment Certificates for an `Admin` identity / role for the organization.
In order to create the local node MSP, we must first register and enroll the node identities with the ECert CAs, and
then organize the TLS and MSP certificates into a location suitable for launching the network services.
The key steps in this process are:
The key steps in this process are:
- [Registering and enrolling identities with a CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/use_CA.html#registering-and-enrolling-identities-with-a-ca)
- [Create the local MSP of a node](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/use_CA.html#create-the-local-msp-of-a-node)
In the test network, each organization includes a function that wraps the registration, enrollment, and MSP aggregation
into a series of fabric-ca-client calls. [The script](../scripts/test_network.sh) will be executed directly on the
org's ECert CA pod, with access to the persistent volume for storage of the MSP and TLS certificates. While this is
largely boilerplate scripting, the process is straightforward: For each node in the network, we'll use the CAs to
generate TLS+MSP certificates, bundling into an MSP with a `config.yaml` specifying the fabric roles associated with
the target usage in the network.
In the test network, each organization includes a function that wraps the registration, enrollment, and MSP aggregation
into a series of fabric-ca-client calls. [The script](../scripts/test_network.sh) will be executed directly on the
org's ECert CA pod, with access to the persistent volume for storage of the MSP and TLS certificates. While this is
largely boilerplate scripting, the process is straightforward: For each node in the network, we'll use the CAs to
generate TLS+MSP certificates, bundling into an MSP with a `config.yaml` specifying the fabric roles associated with
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
# 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-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-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-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-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-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-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-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-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-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-admin:org0adminpw@org0-ecert-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
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-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-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-ca --mspdir /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/users/Admin@org0.example.com/msp
# Create an MSP config.yaml (why is this not generated by the enrollment by fabric-ca-client?)
echo "NodeOUs:
Enable: true
ClientOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem
Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: client
PeerOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem
Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: peer
AdminOUIdentifier:
Certificate: cacerts/org0-ecert-ca.pem
Certificate: cacerts/org0-ca.pem
OrganizationalUnitIdentifier: admin
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
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
@ -89,33 +75,33 @@ cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/o
```
## External Chaincode Builders
## External Chaincode Builders
Running Fabric in Kubernetes places some unique constraints on the Chaincode lifecycle:
Running Fabric in Kubernetes places some unique constraints on the Chaincode lifecycle:
- Many cloud-native vendors rely on [containerd.io](https://containerd.io) to manage the lifecycle of containers
within a cluster. By contrast, Fabric assumes the presence of a Docker daemon to compile and launch chaincode
containers. Without a local Docker daemon, Fabric's default chaincode pipeline is doomed!
- Many cloud-native vendors rely on [containerd.io](https://containerd.io) to manage the lifecycle of containers
within a cluster. By contrast, Fabric assumes the presence of a Docker daemon to compile and launch chaincode
containers. Without a local Docker daemon, Fabric's default chaincode pipeline is doomed!
- For security and operational concerns, it is a "non-starter" to run a docker daemon on Kubernetes worker nodes.
- For cloud-ready development, test, validation, CI/CD, and production practices, the use of the
[Chaincode as a Service](https://hyperledger-fabric.readthedocs.io/en/latest/cc_service.html) pattern provides a
- For cloud-ready development, test, validation, CI/CD, and production practices, the use of the
[Chaincode as a Service](https://hyperledger-fabric.readthedocs.io/en/latest/cc_service.html) pattern provides a
_vastly superior user experience_.
- Running Chaincode builds in Docker in Docker, running in Kubernetes in Docker is ... interesting. Let's
step back and _keep it simple_.
- Running Chaincode builds in Docker in Docker, running in Kubernetes in Docker is ... interesting. Let's
step back and _keep it simple_.
In the Kubernetes Test Network, we've incorporated the default `ccaas` external builder
(See [fabric #2884](https://github.com/hyperledger/fabric/issues/2884)) as an accelerator for working with
Chaincode-as-a-Service on Kubernetes. For `ccaas` smart contracts, when chaincode is installed on a peer, the
(See [fabric #2884](https://github.com/hyperledger/fabric/issues/2884)) as an accelerator for working with
Chaincode-as-a-Service on Kubernetes. For `ccaas` smart contracts, when chaincode is installed on a peer, the
external builder binaries will be invoked, bypassing the reliance on a local Docker daemon running in Kubernetes.
This configuration is accomplished by registering an external builder in the peer core.yaml:
This configuration is accomplished by registering an external builder in the peer core.yaml:
```yaml
externalBuilders:
@ -133,75 +119,68 @@ To trigger the external builder for a chaincode service, set the metadata.json `
}
```
- [x] Pro tip: Use the companion container registry at `localhost:5000` to deploy custom chaincode into the test network.
- [x] Pro tip: Deploy a chaincode with `address: host.docker.internal:9999` and attach your chaincode in a debugger.
- [x] Pro tip: Use the companion container registry at `localhost:5000` to deploy custom chaincode into the test network.
- [x] Pro tip: Deploy a chaincode with `address: host.docker.internal:9999` and attach your chaincode in a debugger.
## Starting Peers and Orderers
## Starting Peers and Orderers
```shell
✅ - Launching orderers ...
✅ - Launching peers ...
```
Once the local MSP structures for the network nodes have been created, the orderers and peers may be launched in the
namespace. System nodes will read base configuration files (orderer.yaml and core.yaml) from the organization
Once the local MSP structures for the network nodes have been created, the orderers and peers may be launched in the
namespace. System nodes will read base configuration files (orderer.yaml and core.yaml) from the organization
config folder, made available in Kubernetes as the `fabric-config${org}` config map.
Each orderer and peer creates one `Deployment`, `Pod`, and `Service` in the namespace. In addition, each org
defines an `orgN-peerM-config` `ConfigMap` with environment variable overrides replacing the default settings
in the core.yaml file. Note that each node's [environment](../kube/org1/org1-peer1.yaml) includes pointers to the
Each orderer and peer creates one `Deployment`, `Pod`, and `Service` in the namespace. In addition, each org
defines an `orgN-peerM-config` `ConfigMap` with environment variable overrides replacing the default settings
in the core.yaml file. Note that each node's [environment](../kube/org1/org1-peer1.yaml) includes pointers to the
node local MSP folders, certificates, and TLS signing keys that we generated above.
Note that the deployment yaml files include some basic template substitution and parameters. For simplicity and
clarity, we elected to use basic string substitution with sed/awk/bash/etc., rather than introduce a Kube template
Note that the deployment yaml files include some basic template substitution and parameters. For simplicity and
clarity, we elected to use basic string substitution with sed/awk/bash/etc., rather than introduce a Kube template
binding system (e.g. Helm, Kustomize, Kapitan, Ansible, etc.) for manipulating yaml templates:
```shell
cat kube/org0/org0-orderer1.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org0/org0-orderer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org0/org0-orderer3.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org0/org0-orderer1.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org0/org0-orderer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org0/org0-orderer3.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
# Wait for the orderers to completely start before launching the network peer nodes.
kubectl -n $NS rollout status deploy/org0-orderer1
kubectl -n $NS rollout status deploy/org0-orderer2
# Wait for the orderers to completely start before launching the network peer nodes.
kubectl -n $NS rollout status deploy/org0-orderer1
kubectl -n $NS rollout status deploy/org0-orderer2
kubectl -n $NS rollout status deploy/org0-orderer3
cat kube/org1/org1-peer1.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org1/org1-peer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org2/org2-peer1.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 -
cat kube/org1/org1-peer1.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org1/org1-peer2.yaml | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' | kubectl -n $NS -f -
cat kube/org2/org2-peer1.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 :
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):
- TLS Certificate Authority : https://org0-tls-ca
- ECert Certificate Authority : https://org0-ecert-ca
- Org0 (org0.example.com):
- ECert Certificate Authority : https://org0-ca
- Orderer1 : grpcs://org0-orderer1
- Orderer2 : grpcs://org0-orderer2
- Orderer3 : grpcs://org0-orderer3
- Org1 (org1.example.com):
- TLS Certificate Authority : https://org1-tls-ca
- ECert Certificate Authority : https://org1-ecert-ca
- ECert Certificate Authority : https://org1-ca
- Peer Node 1 : grpcs://org1-peer1
- Peer Node 2 : grpcs://org1-peer2
- Org2 (org2.example.com):
- TLS Certificate Authority : https://org2-tls-ca
- ECert Certificate Authority : https://org2-ecert-ca
- ECert Certificate Authority : https://org2-ca
- Peer Node 1 : grpcs://org2-peer1
- Peer Node 2 : grpcs://org2-peer2
### Next : [Working With Channels](CHANNELS.md)

View file

@ -27,30 +27,30 @@ data:
"Org1": {
"mspid": "Org1MSP",
"peers": [
"org1-peer1"
"org1-peers"
],
"certificateAuthorities": [
"org1-ecert"
"org1-ca"
]
}
},
"peers": {
"org1-peer1": {
"url": "grpcs://org1-peer1:7051",
"org1-peers": {
"url": "grpcs://org1-peer-gateway-svc:7051",
"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"
},
"grpcOptions": {
"grpc-wait-for-ready-timeout": 100000,
"ssl-target-name-override": "org1-peer1",
"hostnameOverride": "org1-peer1"
"ssl-target-name-override": "org1-peer-gateway-svc",
"hostnameOverride": "org1-peer-gateway-svc"
}
}
},
"certificateAuthorities": {
"org1-ecert-ca": {
"url": "https://org1-ecert-ca",
"caName": "org1-ecert-ca",
"org1-ca": {
"url": "https://org1-ca",
"caName": "org1-ca",
"tlsCACerts": {
"pem": "TODO"
},
@ -103,29 +103,29 @@ data:
"Org2": {
"mspid": "Org2MSP",
"peers": [
"org2-peer1"
"org2-peers"
],
"certificateAuthorities": [
"org2-ecert-ca"
"org2-ca"
]
}
},
"peers": {
"org2-peer1": {
"url": "grpcs://org2-peer1:7051",
"org2-peers": {
"url": "grpcs://org2-peer-gateway-svc:7051",
"tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc6gAwIBAgIUJJ4wGOSCfw8XOOIx29o67wBpFB4wCgYIKoZIzj0EAwIw\\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\\nY2Etc2VydmVyMB4XDTIxMDkyMDExNDEwMFoXDTM2MDkxNjExNDEwMFowaDELMAkG\\nA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBl\\ncmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMtY2Etc2Vy\\ndmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyzGJLZX6pe59QAIBacjfzU4I\\nHezBYLyEu4ySpFx4xwxNLE4BWqLhB1VaOuenSQATM8pmSAy7i1830oM9elKWK6NW\\nMFQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\\nFEoAAhmjq/3M8CFPc7N8SL53erL5MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E\\nAwIDSAAwRQIhAJQ5PJOT4Gg8oiBU2KthMPkZqOLeu3Li4S3yBpLFgbsgAiB960P2\\nXPMu3HLoNXrktYOL9JzWlGyYRSPAnkap5Bsj0w==\\n-----END CERTIFICATE-----\\n"
},
"grpcOptions": {
"ssl-target-name-override": "org2-peer1",
"hostnameOverride": "org2-peer1"
"ssl-target-name-override": "org2-peer-gateway-svc",
"hostnameOverride": "org2-peer-gateway-svc"
}
}
},
"certificateAuthorities": {
"org2-ecert-ca": {
"url": "https://org2-ecert-ca",
"caName": "org2-ecert-ca",
"org2-ca": {
"url": "https://org2-ca",
"caName": "org2-ca",
"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"]
},
@ -179,7 +179,7 @@ spec:
spec:
containers:
- name: main
image: ghcr.io/hyperledgendary/fabric-rest-sample
image: ghcr.io/hyperledger/fabric-rest-sample
imagePullPolicy: IfNotPresent
env:
- name: LOG_LEVEL
@ -236,23 +236,26 @@ spec:
selector:
app: fabric-rest-sample
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60s
labels:
app: fabric-rest-sample
name: fabric-rest-sample
# annotations:
# nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
ingressClassName: nginx
rules:
- http:
- host: fabric-rest-sample.${DOMAIN}
http:
paths:
# - path: "/fabric-rest-sample/(.*)"
- path: "/"
pathType: Prefix
backend:
- backend:
service:
name: fabric-rest-sample
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
#
---
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
kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP
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_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/signcerts/cert.pem
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_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/tls/keystore/server.key
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
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_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer1/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec:
containers:
- name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}}
image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes:
- name: fabric-volume
persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config
configMap:
name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer1-tls-cert
---
apiVersion: v1
kind: Service
@ -82,4 +117,43 @@ spec:
port: 9443
protocol: TCP
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
#
---
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
kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP
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_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/signcerts/cert.pem
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_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/tls/keystore/server.key
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
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_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer2/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec:
containers:
- name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}}
image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes:
- name: fabric-volume
persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config
configMap:
name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer2-tls-cert
---
apiVersion: v1
kind: Service
@ -82,4 +117,43 @@ spec:
port: 9443
protocol: TCP
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
#
---
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
kind: ConfigMap
@ -16,10 +40,16 @@ data:
ORDERER_GENERAL_LOCALMSPID: OrdererMSP
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_CERTIFICATE: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/signcerts/cert.pem
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_PRIVATEKEY: /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/tls/keystore/server.key
ORDERER_GENERAL_TLS_CERTIFICATE: /var/hyperledger/fabric/config/tls/tls.crt
ORDERER_GENERAL_TLS_ROOTCAS: /var/hyperledger/fabric/config/tls/ca.crt
ORDERER_GENERAL_TLS_PRIVATEKEY: /var/hyperledger/fabric/config/tls/tls.key
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_CONSENSUS_WALDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal
ORDERER_CONSENSUS_SNAPDIR: /var/hyperledger/fabric/data/orderer3/etcdraft/wal
@ -43,7 +73,7 @@ spec:
spec:
containers:
- name: main
image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-orderer:{{FABRIC_VERSION}}
image: ${FABRIC_CONTAINER_REGISTRY}/fabric-orderer:${FABRIC_VERSION}
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
@ -57,6 +87,9 @@ spec:
mountPath: /var/hyperledger
- name: fabric-config
mountPath: /var/hyperledger/fabric/config
- name: tls-cert-volume
mountPath: /var/hyperledger/fabric/config/tls
readOnly: true
volumes:
- name: fabric-volume
persistentVolumeClaim:
@ -64,7 +97,9 @@ spec:
- name: fabric-config
configMap:
name: org0-config
- name: tls-cert-volume
secret:
secretName: org0-orderer3-tls-cert
---
apiVersion: v1
kind: Service
@ -82,4 +117,43 @@ spec:
port: 9443
protocol: TCP
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