mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-23 01:55:10 +00:00
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:
parent
62b4131cf5
commit
c31137ecab
185 changed files with 21265 additions and 6485 deletions
|
|
@ -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) |
|
||||
|
|
|
|||
78
asset-transfer-basic/README.md
Normal file
78
asset-transfer-basic/README.md
Normal 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
|
||||
```
|
||||
32
asset-transfer-basic/application-gateway-java/build.gradle
Normal file
32
asset-transfer-basic/application-gateway-java/build.gradle
Normal 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'
|
||||
}
|
||||
BIN
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
||||
234
asset-transfer-basic/application-gateway-java/gradlew
vendored
Executable file
234
asset-transfer-basic/application-gateway-java/gradlew
vendored
Executable 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" "$@"
|
||||
89
asset-transfer-basic/application-gateway-java/gradlew.bat
vendored
Normal file
89
asset-transfer-basic/application-gateway-java/gradlew.bat
vendored
Normal 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
|
||||
|
|
@ -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'
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
@ -18,9 +18,8 @@ ext {
|
|||
}
|
||||
|
||||
repositories {
|
||||
// Use jcenter for resolving dependencies.
|
||||
// You can declare any Maven/Ivy/file repository here.
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,10 +24,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
8902
asset-transfer-basic/rest-api-typescript/package-lock.json
generated
8902
asset-transfer-basic/rest-api-typescript/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
```
|
||||
170
asset-transfer-events/application-gateway-go/app.go
Executable file
170
asset-transfer-events/application-gateway-go/app.go
Executable 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
asset-transfer-events/application-gateway-go/connect.go
Executable file
95
asset-transfer-events/application-gateway-go/connect.go
Executable 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
|
||||
}
|
||||
13
asset-transfer-events/application-gateway-go/go.mod
Normal file
13
asset-transfer-events/application-gateway-go/go.mod
Normal 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
|
||||
)
|
||||
500
asset-transfer-events/application-gateway-go/go.sum
Normal file
500
asset-transfer-events/application-gateway-go/go.sum
Normal 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=
|
||||
20
asset-transfer-events/application-gateway-java/build.gradle
Normal file
20
asset-transfer-events/application-gateway-java/build.gradle
Normal 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'
|
||||
}
|
||||
BIN
asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
||||
234
asset-transfer-events/application-gateway-java/gradlew
vendored
Executable file
234
asset-transfer-events/application-gateway-java/gradlew
vendored
Executable 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" "$@"
|
||||
89
asset-transfer-events/application-gateway-java/gradlew.bat
vendored
Normal file
89
asset-transfer-events/application-gateway-java/gradlew.bat
vendored
Normal 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
|
||||
|
|
@ -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'
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
asset-transfer-events/application-gateway-typescript/.gitignore
vendored
Normal file
14
asset-transfer-events/application-gateway-typescript/.gitignore
vendored
Normal 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
|
||||
32
asset-transfer-events/application-gateway-typescript/package.json
Executable file
32
asset-transfer-events/application-gateway-typescript/package.json
Executable 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"
|
||||
}
|
||||
}
|
||||
157
asset-transfer-events/application-gateway-typescript/src/app.ts
Executable file
157
asset-transfer-events/application-gateway-typescript/src/app.ts
Executable 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
18
asset-transfer-events/application-gateway-typescript/tsconfig.json
Executable file
18
asset-transfer-events/application-gateway-typescript/tsconfig.json
Executable 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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,8 @@ ext {
|
|||
}
|
||||
|
||||
repositories {
|
||||
// Use jcenter for resolving dependencies.
|
||||
// You can declare any Maven/Ivy/file repository here.
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -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","");
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
78
asset-transfer-private-data/README.md
Normal file
78
asset-transfer-private-data/README.md
Normal 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
|
||||
```
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
asset-transfer-private-data/application-gateway-typescript/.gitignore
vendored
Normal file
14
asset-transfer-private-data/application-gateway-typescript/.gitignore
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -24,10 +24,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
127
ci/scripts/run-k8s-test-network-basic.sh
Executable file
127
ci/scripts/run-k8s-test-network-basic.sh
Executable 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.
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
8
ci/scripts/shellcheck.sh
Executable 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
|
||||
31
ci/templates/asset-transfer-basic/azure-pipelines-rest.yml
Normal file
31
ci/templates/asset-transfer-basic/azure-pipelines-rest.yml
Normal 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)
|
||||
9
ci/templates/install-k8s-deps.yml
Normal file
9
ci/templates/install-k8s-deps.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: $(NODE_VER)
|
||||
displayName: Install Node.js
|
||||
|
|
@ -7,7 +7,6 @@ version '0.0.1'
|
|||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ version '0.0.1'
|
|||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ version '0.0.1'
|
|||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
|
|
|
|||
1
test-network-k8s/.gitignore
vendored
1
test-network-k8s/.gitignore
vendored
|
|
@ -3,3 +3,4 @@ network.log
|
|||
network-debug.log
|
||||
build/
|
||||
.env
|
||||
bin/
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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/```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
124
test-network-k8s/kube/org0/org0-ca.yaml
Normal file
124
test-network-k8s/kube/org0/org0-ca.yaml
Normal 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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue