diff --git a/README.md b/README.md index 96dd5463..1e51ef4a 100644 --- a/README.md +++ b/README.md @@ -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) | diff --git a/asset-transfer-basic/README.md b/asset-transfer-basic/README.md new file mode 100644 index 00000000..94bb2668 --- /dev/null +++ b/asset-transfer-basic/README.md @@ -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 +``` \ No newline at end of file diff --git a/asset-transfer-basic/application-gateway-java/build.gradle b/asset-transfer-basic/application-gateway-java/build.gradle new file mode 100644 index 00000000..93ce8b47 --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/build.gradle @@ -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' +} diff --git a/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar b/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7454180f Binary files /dev/null and b/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties b/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e750102e --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/asset-transfer-basic/application-gateway-java/gradlew b/asset-transfer-basic/application-gateway-java/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/gradlew @@ -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" "$@" diff --git a/asset-transfer-basic/application-gateway-java/gradlew.bat b/asset-transfer-basic/application-gateway-java/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/gradlew.bat @@ -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 diff --git a/asset-transfer-basic/application-gateway-java/settings.gradle b/asset-transfer-basic/application-gateway-java/settings.gradle new file mode 100644 index 00000000..5423bc7d --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/settings.gradle @@ -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' diff --git a/asset-transfer-basic/application-gateway-java/src/main/java/App.java b/asset-transfer-basic/application-gateway-java/src/main/java/App.java new file mode 100644 index 00000000..436c6c67 --- /dev/null +++ b/asset-transfer-basic/application-gateway-java/src/main/java/App.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 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()); + } + } +} diff --git a/asset-transfer-basic/application-gateway-typescript/src/app.ts b/asset-transfer-basic/application-gateway-typescript/src/app.ts index bd4dff49..3dda3fff 100644 --- a/asset-transfer-basic/application-gateway-typescript/src/app.ts +++ b/asset-transfer-basic/application-gateway-typescript/src/app.ts @@ -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 { + + 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 { } } -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 { 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{ 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 { + 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}`); +} \ No newline at end of file diff --git a/asset-transfer-basic/application-java/build.gradle b/asset-transfer-basic/application-java/build.gradle index e2eed626..a204249d 100644 --- a/asset-transfer-basic/application-java/build.gradle +++ b/asset-transfer-basic/application-java/build.gradle @@ -18,9 +18,8 @@ ext { } repositories { - // Use jcenter for resolving dependencies. // You can declare any Maven/Ivy/file repository here. - jcenter() + mavenCentral() } dependencies { diff --git a/asset-transfer-basic/chaincode-java/Dockerfile b/asset-transfer-basic/chaincode-java/Dockerfile index 79ade7a5..ca543d67 100755 --- a/asset-transfer-basic/chaincode-java/Dockerfile +++ b/asset-transfer-basic/chaincode-java/Dockerfile @@ -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 diff --git a/asset-transfer-basic/chaincode-java/build.gradle b/asset-transfer-basic/chaincode-java/build.gradle index f6632b38..ba6d051b 100644 --- a/asset-transfer-basic/chaincode-java/build.gradle +++ b/asset-transfer-basic/chaincode-java/build.gradle @@ -24,10 +24,7 @@ dependencies { } repositories { - maven { - url "https://hyperledger.jfrog.io/hyperledger/fabric-maven" - } - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } diff --git a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js index beb87239..57c35ee3 100644 --- a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js +++ b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js @@ -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; } diff --git a/asset-transfer-basic/chaincode-typescript/Dockerfile b/asset-transfer-basic/chaincode-typescript/Dockerfile index 22c35459..467120a7 100644 --- a/asset-transfer-basic/chaincode-typescript/Dockerfile +++ b/asset-transfer-basic/chaincode-typescript/Dockerfile @@ -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 diff --git a/asset-transfer-basic/rest-api-typescript/README.md b/asset-transfer-basic/rest-api-typescript/README.md index e2314dc5..ab928154 100644 --- a/asset-transfer-basic/rest-api-typescript/README.md +++ b/asset-transfer-basic/rest-api-typescript/README.md @@ -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... diff --git a/asset-transfer-basic/rest-api-typescript/demo.http b/asset-transfer-basic/rest-api-typescript/demo.http index 13ebfe99..1ace06ba 100644 --- a/asset-transfer-basic/rest-api-typescript/demo.http +++ b/asset-transfer-basic/rest-api-typescript/demo.http @@ -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" } ] diff --git a/asset-transfer-basic/rest-api-typescript/docker-compose.yaml b/asset-transfer-basic/rest-api-typescript/docker-compose.yaml index b7c4b0b3..3060b591 100644 --- a/asset-transfer-basic/rest-api-typescript/docker-compose.yaml +++ b/asset-transfer-basic/rest-api-typescript/docker-compose.yaml @@ -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 diff --git a/asset-transfer-basic/rest-api-typescript/package-lock.json b/asset-transfer-basic/rest-api-typescript/package-lock.json index 4960c535..5c4363f0 100644 --- a/asset-transfer-basic/rest-api-typescript/package-lock.json +++ b/asset-transfer-basic/rest-api-typescript/package-lock.json @@ -1,8 +1,8850 @@ { "name": "asset-transfer-basic", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "asset-transfer-basic", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "bullmq": "^1.47.2", + "dotenv": "^10.0.0", + "env-var": "^7.0.1", + "express": "^4.17.1", + "express-validator": "^6.12.0", + "fabric-network": "^2.2.10", + "helmet": "^4.6.0", + "http-status-codes": "^2.1.4", + "ioredis": "^4.27.8", + "passport": "^0.4.1", + "passport-headerapikey": "^1.2.2", + "pino": "^6.11.3", + "pino-http": "^5.5.0", + "source-map-support": "^0.5.19" + }, + "devDependencies": { + "@types/express": "^4.17.12", + "@types/ioredis": "^4.26.4", + "@types/jest": "^26.0.24", + "@types/node": "^15.14.7", + "@types/passport": "^1.0.7", + "@types/pino": "^6.3.8", + "@types/pino-http": "^5.4.1", + "@types/supertest": "^2.0.11", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "@typescript-eslint/parser": "^4.28.0", + "eslint": "^7.29.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0", + "ioredis-mock": "^5.6.0", + "jest": "^27.0.6", + "jest-mock-extended": "^2.0.2-beta2", + "pino-pretty": "^5.0.2", + "prettier": "^2.3.1", + "rimraf": "^3.0.2", + "supertest": "^6.1.4", + "ts-jest": "^27.0.4", + "ts-node": "^10.1.0", + "typescript": "^4.3.5" + }, + "engines": { + "node": ">=12", + "npm": ">=5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz", + "integrity": "sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.8", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.8", + "@babel/helpers": "^7.14.8", + "@babel/parser": "^7.14.8", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/core/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.8", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-validator-identifier": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", + "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.8.tgz", + "integrity": "sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.8", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.8", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", + "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@grpc/grpc-js": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.4.tgz", + "integrity": "sha512-a6222b7Dl6fIlMgzVl7e+NiRoLiZFbpcwvBH2Oli56Bn7W4/3Ld+86hK4ffPn5rx2DlDidmIcvIJiOQXyhv9gA==", + "dependencies": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.7.tgz", + "integrity": "sha512-QzTPIyJxU0u+r2qGe8VMl3j/W2ryhEvBv7hc42OjYfthSj370fUrb7na65rG6w3YLZS/fb8p89iTBobfWGDgdw==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.1.1" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@hapi/bourne": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", + "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.0.6.tgz", + "integrity": "sha512-fMlIBocSHPZ3JxgWiDNW/KPj6s+YRd0hicb33IrmelCcjXo/pXPwvuiKFmZz+XuqI/1u7nbUK10zSsWL/1aegg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.0.6", + "jest-util": "^27.0.6", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/core": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.0.6.tgz", + "integrity": "sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow==", + "dev": true, + "dependencies": { + "@jest/console": "^27.0.6", + "@jest/reporters": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.0.6", + "jest-config": "^27.0.6", + "jest-haste-map": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-resolve-dependencies": "^27.0.6", + "jest-runner": "^27.0.6", + "jest-runtime": "^27.0.6", + "jest-snapshot": "^27.0.6", + "jest-util": "^27.0.6", + "jest-validate": "^27.0.6", + "jest-watcher": "^27.0.6", + "micromatch": "^4.0.4", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/environment": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.0.6.tgz", + "integrity": "sha512-4XywtdhwZwCpPJ/qfAkqExRsERW+UaoSRStSHCCiQTUpoYdLukj+YJbQSFrZjhlUDRZeNiU9SFH0u7iNimdiIg==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "jest-mock": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.0.6.tgz", + "integrity": "sha512-sqd+xTWtZ94l3yWDKnRTdvTeZ+A/V7SSKrxsrOKSqdyddb9CeNRF8fbhAU0D7ZJBpTTW2nbp6MftmKJDZfW2LQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "@sinonjs/fake-timers": "^7.0.2", + "@types/node": "*", + "jest-message-util": "^27.0.6", + "jest-mock": "^27.0.6", + "jest-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/globals": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.0.6.tgz", + "integrity": "sha512-DdTGCP606rh9bjkdQ7VvChV18iS7q0IMJVP1piwTWyWskol4iqcVwthZmoJEf7obE1nc34OpIyoVGPeqLC+ryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.0.6", + "@jest/types": "^27.0.6", + "expect": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/reporters": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.0.6.tgz", + "integrity": "sha512-TIkBt09Cb2gptji3yJXb3EE+eVltW6BjO7frO7NEfjI9vSIYoISi5R3aI3KpEDXlB1xwB+97NXIqz84qYeYsfA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-util": "^27.0.6", + "jest-worker": "^27.0.6", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/source-map": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", + "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.0.6.tgz", + "integrity": "sha512-ja/pBOMTufjX4JLEauLxE3LQBPaI2YjGFtXexRAjt1I/MbfNlMx0sytSX3tn5hSLzQsR3Qy2rd0hc1BWojtj9w==", + "dev": true, + "dependencies": { + "@jest/console": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.0.6.tgz", + "integrity": "sha512-bISzNIApazYOlTHDum9PwW22NOyDa6VI31n6JucpjTVM0jD6JDgqEZ9+yn575nDdPF0+4csYDxNNW13NvFQGZA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.0.6", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.0.6", + "jest-runtime": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.0.6.tgz", + "integrity": "sha512-rj5Dw+mtIcntAUnMlW/Vju5mr73u8yg+irnHwzgtgoeI6cCPOvUwQ0D1uQtc/APmWgvRweEb1g05pkUpxH3iCA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.0.6", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.0.6", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.0.6", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.1.15", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", + "integrity": "sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.34", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", + "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", + "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ioredis": { + "version": "4.26.6", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.26.6.tgz", + "integrity": "sha512-Q9ydXL/5Mot751i7WLCm9OGTj5jlW3XBdkdEW21SkXZ8Y03srbkluFGbM3q8c+vzPW30JOLJ+NsZWHoly0+13A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "26.0.24", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", + "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", + "dev": true, + "dependencies": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "15.14.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.7.tgz", + "integrity": "sha512-FA45p37/mLhpebgbPWWCKfOisTjxGK9lwcHlJ6XVLfu3NgfcazOJHdYUZCWPMK8QX4LhNZdmfo6iMz9FqpUbaw==" + }, + "node_modules/@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/pino": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.8.tgz", + "integrity": "sha512-E47CmRy1FNMaCN8r0d8ECQOjXen9O0p6GGsUjLfmawlxRKosZ82WP1oWVKj+ikTkMDHxWzN5BuKmplo44ynrIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/pino-pretty": "*", + "@types/pino-std-serializers": "*", + "@types/sonic-boom": "*" + } + }, + "node_modules/@types/pino-http": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@types/pino-http/-/pino-http-5.4.1.tgz", + "integrity": "sha512-G/iRh3egjicSm6DPomAfFel0fUsuwKEd4vtLALSEohravku684VHhO3W14UibyHo7gWW0F1v4LxGR/pe27cNdA==", + "dev": true, + "dependencies": { + "@types/pino": "*" + } + }, + "node_modules/@types/pino-pretty": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/pino-pretty/-/pino-pretty-4.7.0.tgz", + "integrity": "sha512-fIZ+VXf9gJoJR4tiiM7G+j/bZkPoZEfFGzA4d8tAWCTpTVyvVaBwnmdLs3wEXYpMjw8eXulrOzNCjmGHT3FgHw==", + "dev": true, + "dependencies": { + "@types/pino": "*" + } + }, + "node_modules/@types/pino-std-serializers": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/pino-std-serializers/-/pino-std-serializers-2.4.1.tgz", + "integrity": "sha512-17XcksO47M24IVTVKPeAByWUd3Oez7EbIjXpSbzMPhXVzgjGtrOa49gKBwxH9hb8dKv58OelsWQ+A1G1l9S3wQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "node_modules/@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sonic-boom": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/sonic-boom/-/sonic-boom-0.7.0.tgz", + "integrity": "sha512-AfqR0fZMoUXUNwusgXKxcE9DPlHNDHQp6nKYUd4PSRpLobF5CCevSpyTEBcVZreqaWKCnGBr9KI1fHMTttoB7A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/superagent": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.12.tgz", + "integrity": "sha512-1GQvD6sySQPD6p9EopDFI3f5OogdICl1sU/2ij3Esobz/RtL9fWZZDPmsuv7eiy5ya+XNiPAxUcI3HIUTJa+3A==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" + }, + "node_modules/@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", + "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", + "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", + "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/typescript-estree": "4.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", + "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", + "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", + "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/visitor-keys": "4.28.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", + "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.28.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "dev": true, + "dependencies": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/args/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/args/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/axios-cookiejar-support": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz", + "integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==", + "dependencies": { + "is-redirect": "^1.0.0", + "pify": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@types/tough-cookie": ">=2.3.3", + "axios": ">=0.16.2", + "tough-cookie": ">=2.3.3" + } + }, + "node_modules/babel-jest": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.6.tgz", + "integrity": "sha512-iTJyYLNc4wRofASmofpOc5NK9QunwMk+TLFgGXsTFS8uEqmd8wdI7sga0FPe2oVH3b5Agt/EAK1QjPEuKL8VfA==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.0.6", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz", + "integrity": "sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz", + "integrity": "sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.0.6", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "node_modules/bullmq": { + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-1.47.2.tgz", + "integrity": "sha512-IMzWjXdw6B5RSqPyEiOvoA0efjfTFx2DuB1N+z3T2wYcOVLIcIFybbFjhqVn9Sv/Zb5l6TpuFiU52P+C+/DpNA==", + "dependencies": { + "@types/ioredis": "^4.27.0", + "cron-parser": "^2.7.3", + "get-port": "^5.0.0", + "ioredis": "^4.27.8", + "lodash": "^4.17.21", + "semver": "^6.3.0", + "tslib": "^1.10.0", + "uuid": "^8.3.2" + } + }, + "node_modules/bullmq/node_modules/@types/ioredis": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.27.4.tgz", + "integrity": "sha512-uTAA/woL//GxXQI1e9FuUoDZCpP8yn5LXQdea1IEFyLtb8GP2w3HfOE+SqglF6QSAp/3cZLWzrMhHqWSYI3bfg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/bullmq/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/bullmq/node_modules/ioredis": { + "version": "4.27.9", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.9.tgz", + "integrity": "sha512-hAwrx9F+OQ0uIvaJefuS3UTqW+ByOLyLIV+j0EH8ClNVxvFyH9Vmb08hCL4yje6mDYT5zMquShhypkd50RRzkg==", + "dependencies": { + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.1", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "lodash.isarguments": "^3.1.0", + "p-map": "^2.1.0", + "redis-commands": "1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/bullmq/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/bullmq/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001248", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz", + "integrity": "sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cron-parser": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz", + "integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==", + "dependencies": { + "is-nan": "^1.3.0", + "moment-timezone": "^0.5.31" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/dateformat": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz", + "integrity": "sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/electron-to-chromium": { + "version": "1.3.789", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.789.tgz", + "integrity": "sha512-lK4xn6C6ZF1kgLaC/EhOtC1MSKENExj3rMwGVnBTfHW81Z/Hb1Rge5YaWawN/YOXy3xCaESuE4KWSD50kOZ9rQ==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-var": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/env-var/-/env-var-7.0.1.tgz", + "integrity": "sha512-w4iTR5nongmpSgIByBhEaMvuLZOQCyzv4IUbhZnYMSKo/X8tj9E2Wdn4ikzKNFi29K78e5eT64iQkpar+nIYzw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.6.tgz", + "integrity": "sha512-psNLt8j2kwg42jGBDSfAlU49CEZxejN1f1PlANWDZqIhBOVU/c2Pm888FcjWJzFewhIsNWfZJeLjUjtKGiPuSw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-regex-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/expect/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-validator": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.12.0.tgz", + "integrity": "sha512-lcQAdVeAO+pBbHD33nIsDsd+QPakLX08tJ82iEsXj6ezyWCfYjE9RY/g9SVq5z4G0NaIkH8039Oe4r0G92DRyA==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "^13.5.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fabric-common": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/fabric-common/-/fabric-common-2.2.10.tgz", + "integrity": "sha512-0FRY8M906D0B/NGyPKDbLoorhHCaxlus5lv9X7+sf+M/UnzBCQIps5XDhO2KJVyq4hP4XUwgJV8zBBpxSmN3iQ==", + "dependencies": { + "callsite": "^1.0.0", + "elliptic": "^6.5.4", + "fabric-protos": "2.2.10", + "js-sha3": "^0.8.0", + "jsrsasign": "^10.4.1", + "long": "^4.0.0", + "nconf": "^0.11.2", + "promise-settle": "^0.3.0", + "sjcl": "^1.0.8", + "winston": "^2.4.5", + "yn": "^4.0.0" + }, + "engines": { + "node": "^10.15.3 || ^12.13.1 || ^14.13.1", + "npm": "^6.4.1" + }, + "optionalDependencies": { + "pkcs11js": "^1.0.6" + } + }, + "node_modules/fabric-network": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/fabric-network/-/fabric-network-2.2.10.tgz", + "integrity": "sha512-S6ITwBoLTfR9mokWqmAoHW2++VL1F5NS4LMJKz/tXbCPJcg6JyQGn07/OOHtqESchVKbadzvwrahy93s3CBFmQ==", + "dependencies": { + "fabric-common": "2.2.10", + "fabric-protos": "2.2.10", + "long": "^4.0.0", + "nano": "^9.0.3" + }, + "engines": { + "node": "^10.15.3 || ^12.13.1 || ^14.13.1", + "npm": "^6.4.1" + } + }, + "node_modules/fabric-protos": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/fabric-protos/-/fabric-protos-2.2.10.tgz", + "integrity": "sha512-6ApPgneH/UxsB9QbwPzHEucsCVMnwacyuyHTYxpfj0/ZydWIoNThNsSJEfBdmwhupLG5w5vVup/q/CvhVw3Vmg==", + "dependencies": { + "@grpc/grpc-js": "^1.3.4", + "@grpc/proto-loader": "^0.6.2", + "protobufjs": "^6.11.2" + }, + "engines": { + "node": "^10.15.3 || ^12.13.1 || ^14.13.1", + "npm": "^6.4.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fast-redact": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.1.tgz", + "integrity": "sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fengari": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz", + "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==", + "dev": true, + "dependencies": { + "readline-sync": "^1.4.9", + "sprintf-js": "^1.1.1", + "tmp": "^0.0.33" + } + }, + "node_modules/fengari-interop": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.2.tgz", + "integrity": "sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ==", + "dev": true, + "peerDependencies": { + "fengari": "^0.1.0" + } + }, + "node_modules/fengari/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatstr": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", + "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/helmet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/http-status-codes": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", + "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ioredis": { + "version": "4.27.9", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.9.tgz", + "integrity": "sha512-hAwrx9F+OQ0uIvaJefuS3UTqW+ByOLyLIV+j0EH8ClNVxvFyH9Vmb08hCL4yje6mDYT5zMquShhypkd50RRzkg==", + "dependencies": { + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.1", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "lodash.isarguments": "^3.1.0", + "p-map": "^2.1.0", + "redis-commands": "1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ioredis-mock": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-5.6.0.tgz", + "integrity": "sha512-Ow+tyKdijg/gA2gSEv7lq8dLp6bO7FnwDXbJ9as37NF23XNRGMLzBc7ITaqMydfrbTodWnLcE2lKEaBs7SBpyA==", + "dev": true, + "dependencies": { + "fengari": "^0.1.4", + "fengari-interop": "^0.1.2", + "lodash": "^4.17.21", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ioredis": "4.x", + "redis-commands": "1.x" + } + }, + "node_modules/ioredis/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.1.1" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.6.tgz", + "integrity": "sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA==", + "dev": true, + "dependencies": { + "@jest/core": "^27.0.6", + "import-local": "^3.0.2", + "jest-cli": "^27.0.6" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.0.6.tgz", + "integrity": "sha512-BuL/ZDauaq5dumYh5y20sn4IISnf1P9A0TDswTxUi84ORGtVa86ApuBHqICL0vepqAnZiY6a7xeSPWv2/yy4eA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-circus": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.0.6.tgz", + "integrity": "sha512-OJlsz6BBeX9qR+7O9lXefWoc2m9ZqcZ5Ohlzz0pTEAG4xMiZUJoacY8f4YDHxgk0oKYxj277AfOk9w6hZYvi1Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.0.6", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.0.6", + "jest-matcher-utils": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-runtime": "^27.0.6", + "jest-snapshot": "^27.0.6", + "jest-util": "^27.0.6", + "pretty-format": "^27.0.6", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.0.6.tgz", + "integrity": "sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.0.6", + "@jest/types": "^27.0.6", + "babel-jest": "^27.0.6", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "jest-circus": "^27.0.6", + "jest-environment-jsdom": "^27.0.6", + "jest-environment-node": "^27.0.6", + "jest-get-type": "^27.0.6", + "jest-jasmine2": "^27.0.6", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-runner": "^27.0.6", + "jest-util": "^27.0.6", + "jest-validate": "^27.0.6", + "micromatch": "^4.0.4", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-docblock": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", + "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.0.6.tgz", + "integrity": "sha512-m6yKcV3bkSWrUIjxkE9OC0mhBZZdhovIW5ergBYirqnkLXkyEn3oUUF/QZgyecA1cF1QFyTE8bRRl8Tfg1pfLA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "jest-util": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.0.6.tgz", + "integrity": "sha512-FvetXg7lnXL9+78H+xUAsra3IeZRTiegA3An01cWeXBspKXUhAwMM9ycIJ4yBaR0L7HkoMPaZsozCLHh4T8fuw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.0.6", + "@jest/fake-timers": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "jest-mock": "^27.0.6", + "jest-util": "^27.0.6", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-node": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.0.6.tgz", + "integrity": "sha512-+Vi6yLrPg/qC81jfXx3IBlVnDTI6kmRr08iVa2hFCWmJt4zha0XW7ucQltCAPhSR0FEKEoJ3i+W4E6T0s9is0w==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.0.6", + "@jest/fake-timers": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "jest-mock": "^27.0.6", + "jest-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-haste-map": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.0.6.tgz", + "integrity": "sha512-4ldjPXX9h8doB2JlRzg9oAZ2p6/GpQUNAeiYXqcpmrKbP0Qev0wdZlxSMOmz8mPOEnt4h6qIzXFLDi8RScX/1w==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.0.6", + "jest-worker": "^27.0.6", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.0.6.tgz", + "integrity": "sha512-cjpH2sBy+t6dvCeKBsHpW41mjHzXgsavaFMp+VWRf0eR4EW8xASk1acqmljFtK2DgyIECMv2yCdY41r2l1+4iA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.0.6", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.0.6", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.0.6", + "jest-matcher-utils": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-runtime": "^27.0.6", + "jest-snapshot": "^27.0.6", + "jest-util": "^27.0.6", + "pretty-format": "^27.0.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-jasmine2/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.0.6.tgz", + "integrity": "sha512-2/d6n2wlH5zEcdctX4zdbgX8oM61tb67PQt4Xh8JFAIy6LRKUnX528HulkaG6nD5qDl5vRV1NXejCe1XRCH5gQ==", + "dev": true, + "dependencies": { + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.6.tgz", + "integrity": "sha512-OFgF2VCQx9vdPSYTHWJ9MzFCehs20TsyFi6bIHbk5V1u52zJOnvF0Y/65z3GLZHKRuTgVPY4Z6LVePNahaQ+tA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.6.tgz", + "integrity": "sha512-rBxIs2XK7rGy+zGxgi+UJKP6WqQ+KrBbD1YMj517HYN3v2BG66t3Xan3FWqYHKZwjdB700KiAJ+iES9a0M+ixw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.0.6", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.0.6", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.0.6.tgz", + "integrity": "sha512-lzBETUoK8cSxts2NYXSBWT+EJNzmUVtVVwS1sU9GwE1DLCfGsngg+ZVSIe0yd0ZSm+y791esiuo+WSwpXJQ5Bw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock-extended": { + "version": "2.0.2-beta2", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.2-beta2.tgz", + "integrity": "sha512-56zcpgRPs3YxQP0ejcaaNFxUinPyRxQCbuk7GGORZqEbAFuQVXWAAtru2tI1N4qcLBoDWEJ/hwUxwbEGY5hdyw==", + "dev": true, + "dependencies": { + "ts-essentials": "^7.0.3" + }, + "peerDependencies": { + "jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0", + "typescript": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", + "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.0.6.tgz", + "integrity": "sha512-yKmIgw2LgTh7uAJtzv8UFHGF7Dm7XfvOe/LQ3Txv101fLM8cx2h1QVwtSJ51Q/SCxpIiKfVn6G2jYYMDNHZteA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "chalk": "^4.0.0", + "escalade": "^3.1.1", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.0.6", + "jest-validate": "^27.0.6", + "resolve": "^1.20.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.6.tgz", + "integrity": "sha512-mg9x9DS3BPAREWKCAoyg3QucCr0n6S8HEEsqRCKSPjPcu9HzRILzhdzY3imsLoZWeosEbJZz6TKasveczzpJZA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-resolve/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runner": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.0.6.tgz", + "integrity": "sha512-W3Bz5qAgaSChuivLn+nKOgjqNxM7O/9JOJoKDCqThPIg2sH/d4A/lzyiaFgnb9V1/w29Le11NpzTJSzga1vyYQ==", + "dev": true, + "dependencies": { + "@jest/console": "^27.0.6", + "@jest/environment": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.0.6", + "jest-environment-node": "^27.0.6", + "jest-haste-map": "^27.0.6", + "jest-leak-detector": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-runtime": "^27.0.6", + "jest-util": "^27.0.6", + "jest-worker": "^27.0.6", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runtime": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.0.6.tgz", + "integrity": "sha512-BhvHLRVfKibYyqqEFkybsznKwhrsu7AWx2F3y9G9L95VSIN3/ZZ9vBpm/XCS2bS+BWz3sSeNGLzI3TVQ0uL85Q==", + "dev": true, + "dependencies": { + "@jest/console": "^27.0.6", + "@jest/environment": "^27.0.6", + "@jest/fake-timers": "^27.0.6", + "@jest/globals": "^27.0.6", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-mock": "^27.0.6", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-snapshot": "^27.0.6", + "jest-util": "^27.0.6", + "jest-validate": "^27.0.6", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-serializer": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", + "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.0.6.tgz", + "integrity": "sha512-NTHaz8He+ATUagUgE7C/UtFcRoHqR2Gc+KDfhQIyx+VFgwbeEMjeP+ILpUTLosZn/ZtbNdCF5LkVnN/l+V751A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.0.6", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.0.6", + "jest-get-type": "^27.0.6", + "jest-haste-map": "^27.0.6", + "jest-matcher-utils": "^27.0.6", + "jest-message-util": "^27.0.6", + "jest-resolve": "^27.0.6", + "jest-util": "^27.0.6", + "natural-compare": "^1.4.0", + "pretty-format": "^27.0.6", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.0.6.tgz", + "integrity": "sha512-1JjlaIh+C65H/F7D11GNkGDDZtDfMEM8EBXsvd+l/cxtgQ6QhxuloOaiayt89DxUvDarbVhqI98HhgrM1yliFQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-validate": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.0.6.tgz", + "integrity": "sha512-yhZZOaMH3Zg6DC83n60pLmdU1DQE46DW+KLozPiPbSbPhlXXaiUTDlhHQhHFpaqIFRrInko1FHXjTRpjWRuWfA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "leven": "^3.1.0", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.0.6.tgz", + "integrity": "sha512-/jIoKBhAP00/iMGnTwUBLgvxkn7vsOweDrOTSPzc7X9uOyUtJIDthQBTI1EXz90bdkrxorUZVhJwiB69gcHtYQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.0.6", + "@jest/types": "^27.0.6", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.0.6", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-worker": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", + "integrity": "sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest/node_modules/jest-cli": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.6.tgz", + "integrity": "sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg==", + "dev": true, + "dependencies": { + "@jest/core": "^27.0.6", + "@jest/test-result": "^27.0.6", + "@jest/types": "^27.0.6", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.0.6", + "jest-util": "^27.0.6", + "jest-validate": "^27.0.6", + "prompts": "^2.0.1", + "yargs": "^16.0.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/joycon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.0.1.tgz", + "integrity": "sha512-SJcJNBg32dGgxhPtM0wQqxqV0ax9k/9TaUskGDSJkSFSQOEWWvQ3zzWdGQRIUry2j1zA5+ReH13t0Mf3StuVZA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz", + "integrity": "sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.5", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/acorn": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", + "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsrsasign": { + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.5.1.tgz", + "integrity": "sha512-yW0fq87KNZFw4Pn5ySllXs3ztZAROQZczEheKZTqmiNpCe/Xj9r5NhuAQ7MXTOyEZGJ/+MPHGTsfbgPFaLpwHQ==", + "funding": { + "url": "https://github.com/kjur/jsrsasign#donations" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "dependencies": { + "tmpl": "1.0.x" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true + }, + "node_modules/nano": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/nano/-/nano-9.0.5.tgz", + "integrity": "sha512-fEAhwAdXh4hDDnC8cYJtW6D8ivOmpvFAqT90+zEuQREpRkzA/mJPcI4EKv15JUdajaqiLTXNoKK6PaRF+/06DQ==", + "dependencies": { + "@types/tough-cookie": "^4.0.0", + "axios": "^0.21.1", + "axios-cookiejar-support": "^1.0.1", + "qs": "^6.9.4", + "tough-cookie": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nano/node_modules/qs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz", + "integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/nconf": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.11.3.tgz", + "integrity": "sha512-iYsAuDS9pzjVMGIzJrGE0Vk3Eh8r/suJanRAnWGBd29rVS2XtSgzcAo5l6asV3e4hH2idVONHirg1efoBOslBg==", + "dependencies": { + "async": "^1.4.0", + "ini": "^2.0.0", + "secure-keys": "^1.0.0", + "yargs": "^16.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node_modules/node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-headerapikey": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/passport-headerapikey/-/passport-headerapikey-1.2.2.tgz", + "integrity": "sha512-4BvVJRrWsNJPrd3UoZfcnnl4zvUWYKEtfYkoDsaOKBsrWHYmzTApCjs7qUbncOLexE9ul0IRiYBFfBG0y9IVQA==", + "dependencies": { + "lodash": "^4.17.15", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.11.3.tgz", + "integrity": "sha512-drPtqkkSf0ufx2gaea3TryFiBHdNIdXKf5LN0hTM82SXI4xVIve2wLwNg92e1MT6m3jASLu6VO7eGY6+mmGeyw==", + "dependencies": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.7", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "quick-format-unescaped": "^4.0.3", + "sonic-boom": "^1.0.2" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-http": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.5.0.tgz", + "integrity": "sha512-ZXhWeYhUisf9oZdS54XaBTrNVzZ7p61/sw0RpwCdU1vI/qdGWvSG4QUA5qU5Y5ya47ch3kM3HTcZf/QB5SCtNw==", + "dependencies": { + "fast-url-parser": "^1.1.3", + "pino": "^6.0.0", + "pino-std-serializers": "^2.4.0" + } + }, + "node_modules/pino-http/node_modules/pino-std-serializers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz", + "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==" + }, + "node_modules/pino-pretty": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-5.1.0.tgz", + "integrity": "sha512-fpDU80MKP59XOWxqV8crTDjRegC2fbDsA56zTr5s1guiv6QuYHILc9x1a4+o9SNPtfmF2kQdpAZS+bIExtbELQ==", + "dev": true, + "dependencies": { + "@hapi/bourne": "^2.0.0", + "@types/node": "^15.3.0", + "args": "^5.0.1", + "chalk": "^4.0.0", + "dateformat": "^4.5.1", + "fast-safe-stringify": "^2.0.7", + "jmespath": "^0.15.0", + "joycon": "^3.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "rfdc": "^1.3.0", + "split2": "^3.1.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" + }, + "node_modules/pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "dependencies": { + "node-modules-regexp": "^1.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkcs11js": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-1.2.6.tgz", + "integrity": "sha512-G3mgp0jcTO2A0fcqPdHEU4xYsmZgztMH10RmtUzTjf3pWxWaX7K3wTWVZInE6OaBucUS3d6St+8eUkqO44Hi3Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/PeculiarVentures" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", + "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/pretty-format/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-settle": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promise-settle/-/promise-settle-0.3.0.tgz", + "integrity": "sha1-tO/VcqHrdM95T4KM00naQKCOTpY=", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz", + "integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", + "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=" + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/sjcl": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz", + "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/sonic-boom": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", + "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . Thanks to @shadowgate15, @spence-s, and @niftylettuce. Superagent is sponsored by Forward Email at .", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 7.0.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/superagent/node_modules/qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supertest": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.4.tgz", + "integrity": "sha512-giC9Zm+Bf1CZP09ciPdUyl+XlMAu6rbch79KYiYKOGcbK2R1wH8h+APul1i/3wN6RF1XfWOIF+8X1ga+7SBrug==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tr46/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-essentials": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", + "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", + "dev": true, + "peerDependencies": { + "typescript": ">=3.7.0" + } + }, + "node_modules/ts-jest": { + "version": "27.0.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.4.tgz", + "integrity": "sha512-c4E1ECy9Xz2WGfTMyHbSaArlIva7Wi2p43QOMmCqjSSjHP06KXv+aT+eSY+yZMuqsMi3k7pyGsGj2q5oSl5WfQ==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@types/jest": "^26.0.0", + "babel-jest": ">=27.0.0 <28", + "jest": "^27.0.0", + "typescript": ">=3.8 <5.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "babel-jest": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz", + "integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==", + "dev": true, + "dependencies": { + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", + "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "dependencies": { + "makeerror": "1.0.x" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", + "integrity": "sha512-TWoamHt5yYvsMarGlGEQE59SbJHqGsZV8/lwC+iCcGeAe0vUaOh+Lv6SYM17ouzC/a/LB1/hz/7sxFBtlu1l4A==", + "dependencies": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", + "integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==", + "engines": { + "node": ">=10" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.12.11", @@ -1578,7 +10420,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -2663,7 +11506,8 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-prettier": { "version": "3.4.0", @@ -3036,7 +11880,8 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.2.tgz", "integrity": "sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ==", - "dev": true + "dev": true, + "requires": {} }, "file-entry-cache": { "version": "6.0.1", @@ -3102,9 +11947,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, "form-data": { "version": "3.0.1", @@ -4443,7 +13288,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "27.0.6", @@ -6140,6 +14986,23 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6160,23 +15023,6 @@ "strip-ansi": "^6.0.0" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -6446,7 +15292,8 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true + "dev": true, + "requires": {} }, "ts-jest": { "version": "27.0.4", @@ -6778,7 +15625,8 @@ "version": "7.5.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/asset-transfer-basic/rest-api-typescript/package.json b/asset-transfer-basic/rest-api-typescript/package.json index c8350cd0..82cbcb07 100644 --- a/asset-transfer-basic/rest-api-typescript/package.json +++ b/asset-transfer-basic/rest-api-typescript/package.json @@ -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", diff --git a/asset-transfer-basic/rest-api-typescript/src/__tests__/api.test.ts b/asset-transfer-basic/rest-api-typescript/src/__tests__/api.test.ts index b1097bc0..4b977f82 100644 --- a/asset-transfer-basic/rest-api-typescript/src/__tests__/api.test.ts +++ b/asset-transfer-basic/rest-api-typescript/src/__tests__/api.test.ts @@ -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( diff --git a/asset-transfer-basic/rest-api-typescript/src/assets.router.ts b/asset-transfer-basic/rest-api-typescript/src/assets.router.ts index 0af215d1..f76deb42 100644 --- a/asset-transfer-basic/rest-api-typescript/src/assets.router.ts +++ b/asset-transfer-basic/rest-api-typescript/src/assets.router.ts @@ -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'); diff --git a/asset-transfer-basic/rest-api-typescript/src/config.ts b/asset-transfer-basic/rest-api-typescript/src/config.ts index 87b64719..c70d70f8 100644 --- a/asset-transfer-basic/rest-api-typescript/src/config.ts +++ b/asset-transfer-basic/rest-api-typescript/src/config.ts @@ -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; -/* +/** * 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; -/* +/** * 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 */ diff --git a/asset-transfer-basic/rest-api-typescript/src/errors.ts b/asset-transfer-basic/rest-api-typescript/src/errors.ts index 010db75f..cb451f9b 100644 --- a/asset-transfer-basic/rest-api-typescript/src/errors.ts +++ b/asset-transfer-basic/rest-api-typescript/src/errors.ts @@ -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! */ diff --git a/asset-transfer-basic/rest-api-typescript/src/fabric.ts b/asset-transfer-basic/rest-api-typescript/src/fabric.ts index 1a9f8b6e..3b91b0c4 100644 --- a/asset-transfer-basic/rest-api-typescript/src/fabric.ts +++ b/asset-transfer-basic/rest-api-typescript/src/fabric.ts @@ -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 => { 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 => { 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 diff --git a/asset-transfer-basic/rest-api-typescript/src/jobs.ts b/asset-transfer-basic/rest-api-typescript/src/jobs.ts index 64307982..51b77b93 100644 --- a/asset-transfer-basic/rest-api-typescript/src/jobs.ts +++ b/asset-transfer-basic/rest-api-typescript/src/jobs.ts @@ -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 diff --git a/asset-transfer-basic/rest-api-typescript/src/redis.ts b/asset-transfer-basic/rest-api-typescript/src/redis.ts index 35865545..9dfc85bf 100644 --- a/asset-transfer-basic/rest-api-typescript/src/redis.ts +++ b/asset-transfer-basic/rest-api-typescript/src/redis.ts @@ -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 diff --git a/asset-transfer-basic/rest-api-typescript/src/server.ts b/asset-transfer-basic/rest-api-typescript/src/server.ts index 60ceb481..110d13d3 100644 --- a/asset-transfer-basic/rest-api-typescript/src/server.ts +++ b/asset-transfer-basic/rest-api-typescript/src/server.ts @@ -19,6 +19,8 @@ const { BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND } = StatusCodes; export const createServer = async (): Promise => { const app = express(); + // Remember for production usage, to check any TLS or CORS requirements + app.use( pinoMiddleware({ logger, diff --git a/asset-transfer-events/README.md b/asset-transfer-events/README.md index 34e90bdd..f591f105 100644 --- a/asset-transfer-events/README.md +++ b/asset-transfer-events/README.md @@ -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 -``` +``` \ No newline at end of file diff --git a/asset-transfer-events/application-gateway-go/app.go b/asset-transfer-events/application-gateway-go/app.go new file mode 100755 index 00000000..4046556c --- /dev/null +++ b/asset-transfer-events/application-gateway-go/app.go @@ -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 + } + } + } +} diff --git a/asset-transfer-events/application-gateway-go/connect.go b/asset-transfer-events/application-gateway-go/connect.go new file mode 100755 index 00000000..4252b4a6 --- /dev/null +++ b/asset-transfer-events/application-gateway-go/connect.go @@ -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 +} diff --git a/asset-transfer-events/application-gateway-go/go.mod b/asset-transfer-events/application-gateway-go/go.mod new file mode 100644 index 00000000..2e0eb816 --- /dev/null +++ b/asset-transfer-events/application-gateway-go/go.mod @@ -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 +) diff --git a/asset-transfer-events/application-gateway-go/go.sum b/asset-transfer-events/application-gateway-go/go.sum new file mode 100644 index 00000000..b5119800 --- /dev/null +++ b/asset-transfer-events/application-gateway-go/go.sum @@ -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= diff --git a/asset-transfer-events/application-gateway-java/build.gradle b/asset-transfer-events/application-gateway-java/build.gradle new file mode 100644 index 00000000..7d8611ae --- /dev/null +++ b/asset-transfer-events/application-gateway-java/build.gradle @@ -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' +} diff --git a/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.jar b/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7454180f Binary files /dev/null and b/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.properties b/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e750102e --- /dev/null +++ b/asset-transfer-events/application-gateway-java/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/asset-transfer-events/application-gateway-java/gradlew b/asset-transfer-events/application-gateway-java/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/asset-transfer-events/application-gateway-java/gradlew @@ -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" "$@" diff --git a/asset-transfer-events/application-gateway-java/gradlew.bat b/asset-transfer-events/application-gateway-java/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/asset-transfer-events/application-gateway-java/gradlew.bat @@ -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 diff --git a/asset-transfer-events/application-gateway-java/settings.gradle b/asset-transfer-events/application-gateway-java/settings.gradle new file mode 100644 index 00000000..f99238e1 --- /dev/null +++ b/asset-transfer-events/application-gateway-java/settings.gradle @@ -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' diff --git a/asset-transfer-events/application-gateway-java/src/main/java/App.java b/asset-transfer-events/application-gateway-java/src/main/java/App.java new file mode 100644 index 00000000..80ba7ab4 --- /dev/null +++ b/asset-transfer-events/application-gateway-java/src/main/java/App.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 eventSession = startChaincodeEventListening()) { + long firstBlockNumber = createAsset(); + updateAsset(); + transferAsset(); + deleteAsset(); + + // Replay events from the block containing the first transaction + replayChaincodeEvents(firstBlockNumber); + } + } + + private CloseableIterator startChaincodeEventListening() { + System.out.println("\n*** Start chaincode event listening"); + + CloseableIterator 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 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; + } + } + } + } +} diff --git a/asset-transfer-events/application-gateway-java/src/main/java/Connections.java b/asset-transfer-events/application-gateway-java/src/main/java/Connections.java new file mode 100644 index 00000000..9cdeab19 --- /dev/null +++ b/asset-transfer-events/application-gateway-java/src/main/java/Connections.java @@ -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); + } +} diff --git a/asset-transfer-events/application-gateway-typescript/.eslintrc.json b/asset-transfer-events/application-gateway-typescript/.eslintrc.json new file mode 100644 index 00000000..fabcde92 --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/.eslintrc.json @@ -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" + ] + } + ] + } \ No newline at end of file diff --git a/asset-transfer-events/application-gateway-typescript/.gitignore b/asset-transfer-events/application-gateway-typescript/.gitignore new file mode 100644 index 00000000..99e5af9f --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/.gitignore @@ -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 diff --git a/asset-transfer-events/application-gateway-typescript/package.json b/asset-transfer-events/application-gateway-typescript/package.json new file mode 100755 index 00000000..d0421b9b --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/package.json @@ -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" + } +} diff --git a/asset-transfer-events/application-gateway-typescript/src/app.ts b/asset-transfer-events/application-gateway-typescript/src/app.ts new file mode 100755 index 00000000..5b7c281c --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/src/app.ts @@ -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 { + 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 | 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> { + 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): Promise { + 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 { + 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 { + 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 { + 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{ + 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 { + 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(); + } +} diff --git a/asset-transfer-events/application-gateway-typescript/src/connect.ts b/asset-transfer-events/application-gateway-typescript/src/connect.ts new file mode 100644 index 00000000..9290c7db --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/src/connect.ts @@ -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 { + 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 { + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +export async function newSigner(): Promise { + 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); +} diff --git a/asset-transfer-events/application-gateway-typescript/tsconfig.json b/asset-transfer-events/application-gateway-typescript/tsconfig.json new file mode 100755 index 00000000..2052fb6e --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/tsconfig.json @@ -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" + ] +} diff --git a/asset-transfer-events/application-javascript/app.js b/asset-transfer-events/application-javascript/app.js index 0f12a385..10e4f367 100644 --- a/asset-transfer-events/application-javascript/app.js +++ b/asset-transfer-events/application-javascript/app.js @@ -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: diff --git a/asset-transfer-events/chaincode-java/build.gradle b/asset-transfer-events/chaincode-java/build.gradle index f59f27c7..839dd2ea 100644 --- a/asset-transfer-events/chaincode-java/build.gradle +++ b/asset-transfer-events/chaincode-java/build.gradle @@ -19,10 +19,7 @@ dependencies { } repositories { - maven { - url "https://hyperledger.jfrog.io/hyperledger/fabric-maven" - } - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } diff --git a/asset-transfer-ledger-queries/application-java/build.gradle b/asset-transfer-ledger-queries/application-java/build.gradle index e2eed626..a204249d 100644 --- a/asset-transfer-ledger-queries/application-java/build.gradle +++ b/asset-transfer-ledger-queries/application-java/build.gradle @@ -18,9 +18,8 @@ ext { } repositories { - // Use jcenter for resolving dependencies. // You can declare any Maven/Ivy/file repository here. - jcenter() + mavenCentral() } dependencies { diff --git a/asset-transfer-ledger-queries/application-java/src/main/java/application/java/App.java b/asset-transfer-ledger-queries/application-java/src/main/java/application/java/App.java index 2a1b74cb..b8cd2799 100644 --- a/asset-transfer-ledger-queries/application-java/src/main/java/application/java/App.java +++ b/asset-transfer-ledger-queries/application-java/src/main/java/application/java/App.java @@ -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",""); diff --git a/asset-transfer-ledger-queries/application-javascript/app.js b/asset-transfer-ledger-queries/application-javascript/app.js index 73fc83a1..f10f400f 100644 --- a/asset-transfer-ledger-queries/application-javascript/app.js +++ b/asset-transfer-ledger-queries/application-javascript/app.js @@ -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'); diff --git a/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go b/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go index 44cbdb1f..fb2a6cb9 100644 --- a/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go +++ b/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go @@ -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 diff --git a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js index 1fe1d29d..fbd92152 100644 --- a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js +++ b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js @@ -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); } diff --git a/asset-transfer-private-data/README.md b/asset-transfer-private-data/README.md new file mode 100644 index 00000000..860acbab --- /dev/null +++ b/asset-transfer-private-data/README.md @@ -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 +``` \ No newline at end of file diff --git a/asset-transfer-private-data/application-gateway-typescript/.eslintrc.json b/asset-transfer-private-data/application-gateway-typescript/.eslintrc.json new file mode 100644 index 00000000..cc7230a8 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/.eslintrc.json @@ -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" + ] + } + ] + } \ No newline at end of file diff --git a/asset-transfer-private-data/application-gateway-typescript/.gitignore b/asset-transfer-private-data/application-gateway-typescript/.gitignore new file mode 100644 index 00000000..99e5af9f --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/.gitignore @@ -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 diff --git a/asset-transfer-private-data/application-gateway-typescript/README.md b/asset-transfer-private-data/application-gateway-typescript/README.md new file mode 100644 index 00000000..94cc79c0 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/README.md @@ -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 + diff --git a/asset-transfer-private-data/application-gateway-typescript/package.json b/asset-transfer-private-data/application-gateway-typescript/package.json new file mode 100644 index 00000000..272fd65c --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/package.json @@ -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" + } +} diff --git a/asset-transfer-private-data/application-gateway-typescript/src/app.ts b/asset-transfer-private-data/application-gateway-typescript/src/app.ts new file mode 100644 index 00000000..6a7e5729 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/src/app.ts @@ -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 { + 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 { + 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 { + // 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 { + 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 { + // 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 { + 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 { + 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 { + 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 { + 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); +} \ No newline at end of file diff --git a/asset-transfer-private-data/application-gateway-typescript/src/connect.ts b/asset-transfer-private-data/application-gateway-typescript/src/connect.ts new file mode 100644 index 00000000..fbf8e250 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/src/connect.ts @@ -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 { + 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 { + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +export async function newSigner(keyDirectoryPath: string): Promise { + 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); +} \ No newline at end of file diff --git a/asset-transfer-private-data/application-gateway-typescript/tsconfig.json b/asset-transfer-private-data/application-gateway-typescript/tsconfig.json new file mode 100644 index 00000000..2052fb6e --- /dev/null +++ b/asset-transfer-private-data/application-gateway-typescript/tsconfig.json @@ -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" + ] +} diff --git a/asset-transfer-private-data/chaincode-java/build.gradle b/asset-transfer-private-data/chaincode-java/build.gradle index 6054373e..bc59a38f 100644 --- a/asset-transfer-private-data/chaincode-java/build.gradle +++ b/asset-transfer-private-data/chaincode-java/build.gradle @@ -24,10 +24,7 @@ dependencies { } repositories { - maven { - url "https://hyperledger.jfrog.io/hyperledger/fabric-maven" - } - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } diff --git a/asset-transfer-sbe/chaincode-java/build.gradle b/asset-transfer-sbe/chaincode-java/build.gradle index e220c114..44f29319 100644 --- a/asset-transfer-sbe/chaincode-java/build.gradle +++ b/asset-transfer-sbe/chaincode-java/build.gradle @@ -27,10 +27,7 @@ dependencies { } repositories { - maven { - url "https://hyperledger.jfrog.io/hyperledger/fabric-maven" - } - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } diff --git a/chaincode/abstore/java/build.gradle b/chaincode/abstore/java/build.gradle index aec70afe..d7bc7ab2 100644 --- a/chaincode/abstore/java/build.gradle +++ b/chaincode/abstore/java/build.gradle @@ -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' } diff --git a/chaincode/fabcar/java/build.gradle b/chaincode/fabcar/java/build.gradle index eb117072..d3eb0f46 100644 --- a/chaincode/fabcar/java/build.gradle +++ b/chaincode/fabcar/java/build.gradle @@ -22,10 +22,7 @@ dependencies { } repositories { - maven { - url "https://hyperledger.jfrog.io/hyperledger/fabric-maven" - } - jcenter() + mavenCentral() maven { url 'https://jitpack.io' } diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index d21a6545..c1ace394 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -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: diff --git a/ci/scripts/run-k8s-test-network-basic.sh b/ci/scripts/run-k8s-test-network-basic.sh new file mode 100755 index 00000000..35e9e93d --- /dev/null +++ b/ci/scripts/run-k8s-test-network-basic.sh @@ -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. diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index 8627c6a6..7fe9185e 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -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" diff --git a/ci/scripts/run-test-network-events.sh b/ci/scripts/run-test-network-events.sh index cc04731f..03242d83 100755 --- a/ci/scripts/run-test-network-events.sh +++ b/ci/scripts/run-test-network-events.sh @@ -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 diff --git a/ci/scripts/run-test-network-private.sh b/ci/scripts/run-test-network-private.sh index af2d22aa..83283925 100755 --- a/ci/scripts/run-test-network-private.sh +++ b/ci/scripts/run-test-network-private.sh @@ -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 diff --git a/ci/scripts/shellcheck.sh b/ci/scripts/shellcheck.sh new file mode 100755 index 00000000..2330162a --- /dev/null +++ b/ci/scripts/shellcheck.sh @@ -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 diff --git a/ci/templates/asset-transfer-basic/azure-pipelines-rest.yml b/ci/templates/asset-transfer-basic/azure-pipelines-rest.yml new file mode 100644 index 00000000..63868ca4 --- /dev/null +++ b/ci/templates/asset-transfer-basic/azure-pipelines-rest.yml @@ -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) diff --git a/ci/templates/install-k8s-deps.yml b/ci/templates/install-k8s-deps.yml new file mode 100644 index 00000000..006a2d41 --- /dev/null +++ b/ci/templates/install-k8s-deps.yml @@ -0,0 +1,9 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +steps: + - task: NodeTool@0 + inputs: + versionSpec: $(NODE_VER) + displayName: Install Node.js diff --git a/commercial-paper/organization/digibank/contract-java/build.gradle b/commercial-paper/organization/digibank/contract-java/build.gradle index c0a2b439..65741879 100644 --- a/commercial-paper/organization/digibank/contract-java/build.gradle +++ b/commercial-paper/organization/digibank/contract-java/build.gradle @@ -7,7 +7,6 @@ version '0.0.1' sourceCompatibility = 1.8 repositories { - mavenLocal() mavenCentral() maven { url 'https://jitpack.io' diff --git a/commercial-paper/organization/digibank/contract-java/shadow-build.gradle b/commercial-paper/organization/digibank/contract-java/shadow-build.gradle index 0a298872..160bf421 100644 --- a/commercial-paper/organization/digibank/contract-java/shadow-build.gradle +++ b/commercial-paper/organization/digibank/contract-java/shadow-build.gradle @@ -12,7 +12,6 @@ version '0.0.1' sourceCompatibility = 1.8 repositories { - mavenLocal() mavenCentral() maven { url 'https://jitpack.io' diff --git a/commercial-paper/organization/magnetocorp/contract-java/build.gradle b/commercial-paper/organization/magnetocorp/contract-java/build.gradle index 0a298872..160bf421 100644 --- a/commercial-paper/organization/magnetocorp/contract-java/build.gradle +++ b/commercial-paper/organization/magnetocorp/contract-java/build.gradle @@ -12,7 +12,6 @@ version '0.0.1' sourceCompatibility = 1.8 repositories { - mavenLocal() mavenCentral() maven { url 'https://jitpack.io' diff --git a/test-network-k8s/.gitignore b/test-network-k8s/.gitignore index b4afee58..c1f650e1 100644 --- a/test-network-k8s/.gitignore +++ b/test-network-k8s/.gitignore @@ -3,3 +3,4 @@ network.log network-debug.log build/ .env +bin/ diff --git a/test-network-k8s/README.md b/test-network-k8s/README.md index 85f46adf..6656d8e1 100644 --- a/test-network-k8s/README.md +++ b/test-network-k8s/README.md @@ -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? diff --git a/test-network-k8s/config/org0/configtx.yaml b/test-network-k8s/config/org0/configtx.yaml index 337c83c2..2981b261 100644 --- a/test-network-k8s/config/org0/configtx.yaml +++ b/test-network-k8s/config/org0/configtx.yaml @@ -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 diff --git a/test-network-k8s/config/org0/fabric-ecert-ca-server-config.yaml b/test-network-k8s/config/org0/fabric-ca-server-config.yaml similarity index 99% rename from test-network-k8s/config/org0/fabric-ecert-ca-server-config.yaml rename to test-network-k8s/config/org0/fabric-ca-server-config.yaml index eff91c34..d2c5fd9b 100644 --- a/test-network-k8s/config/org0/fabric-ecert-ca-server-config.yaml +++ b/test-network-k8s/config/org0/fabric-ca-server-config.yaml @@ -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 diff --git a/test-network-k8s/config/org0/fabric-tls-ca-server-config.yaml b/test-network-k8s/config/org0/fabric-tls-ca-server-config.yaml deleted file mode 100644 index b574e72b..00000000 --- a/test-network-k8s/config/org0/fabric-tls-ca-server-config.yaml +++ /dev/null @@ -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://:@:/ - # 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 -# Automatically generate non-default CAs. The names of these -# additional CAs are "ca1", "ca2", ... "caN", where "N" is -# 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 -# 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 diff --git a/test-network-k8s/config/org2/fabric-ecert-ca-server-config.yaml b/test-network-k8s/config/org1/fabric-ca-server-config.yaml similarity index 99% rename from test-network-k8s/config/org2/fabric-ecert-ca-server-config.yaml rename to test-network-k8s/config/org1/fabric-ca-server-config.yaml index 23732ff8..ccce6f91 100644 --- a/test-network-k8s/config/org2/fabric-ecert-ca-server-config.yaml +++ b/test-network-k8s/config/org1/fabric-ca-server-config.yaml @@ -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 diff --git a/test-network-k8s/config/org1/fabric-tls-ca-server-config.yaml b/test-network-k8s/config/org1/fabric-tls-ca-server-config.yaml deleted file mode 100644 index 23860537..00000000 --- a/test-network-k8s/config/org1/fabric-tls-ca-server-config.yaml +++ /dev/null @@ -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://:@:/ - # 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 -# Automatically generate non-default CAs. The names of these -# additional CAs are "ca1", "ca2", ... "caN", where "N" is -# 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 -# 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 diff --git a/test-network-k8s/config/org1/fabric-ecert-ca-server-config.yaml b/test-network-k8s/config/org2/fabric-ca-server-config.yaml similarity index 99% rename from test-network-k8s/config/org1/fabric-ecert-ca-server-config.yaml rename to test-network-k8s/config/org2/fabric-ca-server-config.yaml index f1ed9da4..992315f0 100644 --- a/test-network-k8s/config/org1/fabric-ecert-ca-server-config.yaml +++ b/test-network-k8s/config/org2/fabric-ca-server-config.yaml @@ -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 diff --git a/test-network-k8s/config/org2/fabric-tls-ca-server-config.yaml b/test-network-k8s/config/org2/fabric-tls-ca-server-config.yaml deleted file mode 100644 index 74879302..00000000 --- a/test-network-k8s/config/org2/fabric-tls-ca-server-config.yaml +++ /dev/null @@ -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://:@:/ - # 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 -# Automatically generate non-default CAs. The names of these -# additional CAs are "ca1", "ca2", ... "caN", where "N" is -# 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 -# 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 diff --git a/test-network-k8s/docs/APPLICATIONS.md b/test-network-k8s/docs/APPLICATIONS.md index 2758f463..dd1f1d7d 100644 --- a/test-network-k8s/docs/APPLICATIONS.md +++ b/test-network-k8s/docs/APPLICATIONS.md @@ -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 diff --git a/test-network-k8s/docs/CA.md b/test-network-k8s/docs/CA.md index c8758b24..2acc922b 100644 --- a/test-network-k8s/docs/CA.md +++ b/test-network-k8s/docs/CA.md @@ -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: ` 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: ` 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. diff --git a/test-network-k8s/docs/CHANNELS.md b/test-network-k8s/docs/CHANNELS.md index 23fa33f6..dacd70cc 100644 --- a/test-network-k8s/docs/CHANNELS.md +++ b/test-network-k8s/docs/CHANNELS.md @@ -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/``` diff --git a/test-network-k8s/docs/HIGH_AVAILABILITY.md b/test-network-k8s/docs/HIGH_AVAILABILITY.md index d23a411a..585f09cc 100644 --- a/test-network-k8s/docs/HIGH_AVAILABILITY.md +++ b/test-network-k8s/docs/HIGH_AVAILABILITY.md @@ -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 diff --git a/test-network-k8s/docs/KUBERNETES.md b/test-network-k8s/docs/KUBERNETES.md index 6570cebd..a1189ebd 100644 --- a/test-network-k8s/docs/KUBERNETES.md +++ b/test-network-k8s/docs/KUBERNETES.md @@ -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) - diff --git a/test-network-k8s/docs/README.md b/test-network-k8s/docs/README.md index cd21bdc9..dab0135f 100644 --- a/test-network-k8s/docs/README.md +++ b/test-network-k8s/docs/README.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. ![Test Network](images/test-network.png) @@ -37,7 +34,7 @@ and ECert CAs for management of local, channel, and user MSP contexts. - [Working with Kubernetes](KUBERNETES.md) - [Certificate Authorities](CA.md) - [Planning for a CA](CA.md#planning-for-a-ca) - - [Deploy the TLS CAs](CA.md#deploy-the-tls-cas) + - [Deploy the TLS CAs](CA.md#deploy-tls-ca-issuers) - [Deploy the ECert CAs](CA.md#deploy-the-organization-ca) - [Launching the Test Network](TEST_NETWORK.md) - [Registering and Enrolling Identities](CA.md#registering-and-enrolling-identities) diff --git a/test-network-k8s/docs/TEST_NETWORK.md b/test-network-k8s/docs/TEST_NETWORK.md index c3832bad..0ab7bbf8 100644 --- a/test-network-k8s/docs/TEST_NETWORK.md +++ b/test-network-k8s/docs/TEST_NETWORK.md @@ -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) - diff --git a/test-network-k8s/kube/fabric-rest-sample.yaml b/test-network-k8s/kube/fabric-rest-sample.yaml index 2bf99d7f..32be0f0a 100644 --- a/test-network-k8s/kube/fabric-rest-sample.yaml +++ b/test-network-k8s/kube/fabric-rest-sample.yaml @@ -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 + diff --git a/test-network-k8s/kube/org0/org0-admin-cli.yaml b/test-network-k8s/kube/org0/org0-admin-cli.yaml deleted file mode 100644 index 85c81343..00000000 --- a/test-network-k8s/kube/org0/org0-admin-cli.yaml +++ /dev/null @@ -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 diff --git a/test-network-k8s/kube/org0/org0-ca.yaml b/test-network-k8s/kube/org0/org0-ca.yaml new file mode 100644 index 00000000..b4e4ac2a --- /dev/null +++ b/test-network-k8s/kube/org0/org0-ca.yaml @@ -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} diff --git a/test-network-k8s/kube/org0/org0-ecert-ca.yaml b/test-network-k8s/kube/org0/org0-ecert-ca.yaml deleted file mode 100644 index 4e1960f5..00000000 --- a/test-network-k8s/kube/org0/org0-ecert-ca.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/test-network-k8s/kube/org0/org0-orderer1.yaml b/test-network-k8s/kube/org0/org0-orderer1.yaml index ce70b59a..f8bf9c36 100644 --- a/test-network-k8s/kube/org0/org0-orderer1.yaml +++ b/test-network-k8s/kube/org0/org0-orderer1.yaml @@ -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 \ No newline at end of file + 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} diff --git a/test-network-k8s/kube/org0/org0-orderer2.yaml b/test-network-k8s/kube/org0/org0-orderer2.yaml index 0314416d..fa96f90e 100644 --- a/test-network-k8s/kube/org0/org0-orderer2.yaml +++ b/test-network-k8s/kube/org0/org0-orderer2.yaml @@ -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 \ No newline at end of file + 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} diff --git a/test-network-k8s/kube/org0/org0-orderer3.yaml b/test-network-k8s/kube/org0/org0-orderer3.yaml index cbca3739..3391f6a8 100644 --- a/test-network-k8s/kube/org0/org0-orderer3.yaml +++ b/test-network-k8s/kube/org0/org0-orderer3.yaml @@ -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 \ No newline at end of file + 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} diff --git a/test-network-k8s/kube/org0/org0-tls-ca.yaml b/test-network-k8s/kube/org0/org0-tls-ca.yaml deleted file mode 100644 index 0ae21a25..00000000 --- a/test-network-k8s/kube/org0/org0-tls-ca.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org0-tls-ca -spec: - replicas: 1 - selector: - matchLabels: - app: org0-tls-ca - template: - metadata: - labels: - app: org0-tls-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-tls-ca" - - name: FABRIC_CA_SERVER_DEBUG - value: "false" - - name: FABRIC_CA_SERVER_HOME - value: "/var/hyperledger/fabric-tls-ca-server" - - 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-tls-ca-server/fabric-ca-server-config.yaml - subPath: fabric-tls-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-tls-ca -spec: - ports: - - name: tls - port: 443 - protocol: TCP - selector: - app: org0-tls-ca \ No newline at end of file diff --git a/test-network-k8s/kube/org0/org0-tls-cert-issuer.yaml b/test-network-k8s/kube/org0/org0-tls-cert-issuer.yaml new file mode 100644 index 00000000..0745afc8 --- /dev/null +++ b/test-network-k8s/kube/org0/org0-tls-cert-issuer.yaml @@ -0,0 +1,34 @@ +# +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org0-tls-cert-issuer +spec: + isCA: true + privateKey: + algorithm: ECDSA + size: 256 + commonName: org0.example.com + secretName: org0-tls-cert-issuer-secret + issuerRef: + name: root-tls-cert-issuer + kind: Issuer + group: cert-manager.io + +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: org0-tls-cert-issuer +spec: + ca: + secretName: org0-tls-cert-issuer-secret + + + + diff --git a/test-network-k8s/kube/org1/org1-admin-cli.yaml b/test-network-k8s/kube/org1/org1-admin-cli.yaml deleted file mode 100644 index 8086e732..00000000 --- a/test-network-k8s/kube/org1/org1-admin-cli.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org1-admin-cli -spec: - replicas: 1 - selector: - matchLabels: - app: org1-admin-cli - template: - metadata: - labels: - app: org1-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 - - name: CORE_PEER_MSPCONFIGPATH - value: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp - - name: CORE_PEER_TLS_ROOTCERT_FILE - value: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/org1-tls-ca.pem - 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-org1 - - name: fabric-config - configMap: - name: org1-config - - name: msp-config - configMap: - name: msp-config diff --git a/test-network-k8s/kube/org1/org1-ca.yaml b/test-network-k8s/kube/org1/org1-ca.yaml new file mode 100644 index 00000000..9428971f --- /dev/null +++ b/test-network-k8s/kube/org1/org1-ca.yaml @@ -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: org1-ca-tls-cert +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org1-ca + - org1-ca.test-network.svc.cluster.local + - org1-ca.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org1-ca-tls-cert + issuerRef: + name: org1-tls-cert-issuer + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: org1-ca +spec: + replicas: 1 + selector: + matchLabels: + app: org1-ca + template: + metadata: + labels: + app: org1-ca + spec: + containers: + - name: main + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-ca:${FABRIC_CA_VERSION} + imagePullPolicy: IfNotPresent + env: + - name: FABRIC_CA_SERVER_CA_NAME + value: "org1-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-org1 + - name: fabric-config + configMap: + name: org1-config + - name: tls-cert-volume + secret: + secretName: org1-ca-tls-cert +--- +apiVersion: v1 +kind: Service +metadata: + name: org1-ca +spec: + ports: + - name: https + port: 443 + protocol: TCP + selector: + app: org1-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: org1-ca + name: org1-ca +spec: + ingressClassName: nginx + rules: + - host: org1-ca.${DOMAIN} + http: + paths: + - backend: + service: + name: org1-ca + port: + name: https + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org1-ca.${DOMAIN} + \ No newline at end of file diff --git a/test-network-k8s/kube/org1/org1-ecert-ca.yaml b/test-network-k8s/kube/org1/org1-ecert-ca.yaml deleted file mode 100644 index c4a9f4e6..00000000 --- a/test-network-k8s/kube/org1/org1-ecert-ca.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org1-ecert-ca -spec: - replicas: 1 - selector: - matchLabels: - app: org1-ecert-ca - template: - metadata: - labels: - app: org1-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: "org1-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-org1 - - name: fabric-config - configMap: - name: org1-config - ---- -apiVersion: v1 -kind: Service -metadata: - name: org1-ecert-ca -spec: - ports: - - name: tls - port: 443 - protocol: TCP - selector: - app: org1-ecert-ca \ No newline at end of file diff --git a/test-network-k8s/kube/org1/org1-peer1.yaml b/test-network-k8s/kube/org1/org1-peer1.yaml index 0b6376db..3f64b04a 100644 --- a/test-network-k8s/kube/org1/org1-peer1.yaml +++ b/test-network-k8s/kube/org1/org1-peer1.yaml @@ -3,6 +3,30 @@ # # SPDX-License-Identifier: Apache-2.0 # +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org1-peer1-tls-cert + namespace: test-network +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org1-peer1 + - org1-peer1.test-network.svc.cluster.local + - org1-peer1.${DOMAIN} + - org1-peer-gateway-svc + - org1-peer-gateway-svc.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org1-peer1-tls-cert + issuerRef: + name: org1-tls-cert-issuer + --- apiVersion: v1 kind: ConfigMap @@ -12,9 +36,9 @@ data: FABRIC_CFG_PATH: /var/hyperledger/fabric/config FABRIC_LOGGING_SPEC: "debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info" CORE_PEER_TLS_ENABLED: "true" - CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls/signcerts/cert.pem - CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls/keystore/server.key - CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls/cacerts/org1-tls-ca.pem + CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/config/tls/tls.crt + CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/config/tls/tls.key + CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/config/tls/ca.crt CORE_PEER_ID: org1-peer1.org1.example.com CORE_PEER_ADDRESS: org1-peer1:7051 CORE_PEER_LISTENADDRESS: 0.0.0.0:7051 @@ -29,6 +53,11 @@ data: CORE_PEER_FILESYSTEMPATH: /var/hyperledger/fabric/data/org1-peer1.org1.example.com CORE_LEDGER_SNAPSHOTS_ROOTDIR: /var/hyperledger/fabric/data/org1-peer1.org1.example.com/snapshots CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG: "{\"peername\":\"org1peer1\"}" + CORE_LEDGER_STATE_STATEDATABASE: CouchDB + CORE_LEDGER_STATE_COUCHDBCONFIG_MAXRETRIESONSTARTUP: "20" + CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS: localhost:5984 + CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME: admin + CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD: adminpw --- apiVersion: apps/v1 kind: Deployment @@ -47,7 +76,7 @@ spec: spec: containers: - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-peer:{{FABRIC_VERSION}} + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:${FABRIC_VERSION} imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -61,6 +90,19 @@ spec: mountPath: /var/hyperledger - name: fabric-config mountPath: /var/hyperledger/fabric/config + - name: tls-cert-volume + mountPath: /var/hyperledger/fabric/config/tls + readOnly: true + - name: couchdb + image: couchdb:3.2.1 + imagePullPolicy: IfNotPresent + env: + - name: "COUCHDB_USER" + value: "admin" + - name: "COUCHDB_PASSWORD" + value: "adminpw" + ports: + - containerPort: 5984 volumes: - name: fabric-volume persistentVolumeClaim: @@ -68,7 +110,9 @@ spec: - name: fabric-config configMap: name: org1-config - + - name: tls-cert-volume + secret: + secretName: org1-peer1-tls-cert --- apiVersion: v1 kind: Service @@ -76,7 +120,7 @@ metadata: name: org1-peer1 spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP - name: chaincode @@ -94,8 +138,46 @@ metadata: name: org1-peer-gateway-svc spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP selector: - org: org1 \ No newline at end of file + org: org1 +--- +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: org1-peer1 + name: org1-peer1 +spec: + ingressClassName: nginx + rules: + - host: org1-peer1.${DOMAIN} + http: + paths: + - backend: + service: + name: org1-peer1 + port: + name: grpc + path: / + pathType: ImplementationSpecific + - host: org1-peer-gateway-svc.${DOMAIN} + http: + paths: + - backend: + service: + name: org1-peer1 + port: + name: grpc + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org1-peer1.${DOMAIN} + - hosts: + - org1-peer-gateway-svc.${DOMAIN} diff --git a/test-network-k8s/kube/org1/org1-peer2.yaml b/test-network-k8s/kube/org1/org1-peer2.yaml index a4073385..288536a8 100644 --- a/test-network-k8s/kube/org1/org1-peer2.yaml +++ b/test-network-k8s/kube/org1/org1-peer2.yaml @@ -3,6 +3,30 @@ # # SPDX-License-Identifier: Apache-2.0 # +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org1-peer2-tls-cert + namespace: test-network +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org1-peer2 + - org1-peer-gateway-svc + - org1-peer2.test-network.svc.cluster.local + - org1-peer2.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org1-peer2-tls-cert + issuerRef: + name: org1-tls-cert-issuer + + --- apiVersion: v1 kind: ConfigMap @@ -12,9 +36,9 @@ data: FABRIC_CFG_PATH: /var/hyperledger/fabric/config FABRIC_LOGGING_SPEC: "debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info" CORE_PEER_TLS_ENABLED: "true" - CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls/signcerts/cert.pem - CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls/keystore/server.key - CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls/cacerts/org1-tls-ca.pem + CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/config/tls/tls.crt + CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/config/tls/tls.key + CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/config/tls/ca.crt CORE_PEER_ID: org1-peer2.org1.example.com CORE_PEER_ADDRESS: org1-peer2:7051 CORE_PEER_LISTENADDRESS: 0.0.0.0:7051 @@ -29,6 +53,11 @@ data: CORE_PEER_FILESYSTEMPATH: /var/hyperledger/fabric/data/org1-peer2.org1.example.com CORE_LEDGER_SNAPSHOTS_ROOTDIR: /var/hyperledger/fabric/data/org1-peer2.org1.example.com/snapshots CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG: "{\"peername\":\"org1peer2\"}" + CORE_LEDGER_STATE_STATEDATABASE: CouchDB + CORE_LEDGER_STATE_COUCHDBCONFIG_MAXRETRIESONSTARTUP: "20" + CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS: localhost:5984 + CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME: admin + CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD: adminpw --- apiVersion: apps/v1 kind: Deployment @@ -47,7 +76,7 @@ spec: spec: containers: - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-peer:{{FABRIC_VERSION}} + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:${FABRIC_VERSION} imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -61,7 +90,19 @@ spec: mountPath: /var/hyperledger - name: fabric-config mountPath: /var/hyperledger/fabric/config - + - name: tls-cert-volume + mountPath: /var/hyperledger/fabric/config/tls + readOnly: true + - name: couchdb + image: couchdb:3.2.1 + imagePullPolicy: IfNotPresent + env: + - name: "COUCHDB_USER" + value: "admin" + - name: "COUCHDB_PASSWORD" + value: "adminpw" + ports: + - containerPort: 5984 volumes: - name: fabric-volume persistentVolumeClaim: @@ -69,7 +110,9 @@ spec: - name: fabric-config configMap: name: org1-config - + - name: tls-cert-volume + secret: + secretName: org1-peer2-tls-cert --- apiVersion: v1 kind: Service @@ -77,7 +120,7 @@ metadata: name: org1-peer2 spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP - name: chaincode @@ -87,4 +130,30 @@ spec: port: 9443 protocol: TCP selector: - app: org1-peer2 \ No newline at end of file + app: org1-peer2 +--- +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: org1-peer2 + name: org1-peer2 +spec: + ingressClassName: nginx + rules: + - host: org1-peer2.${DOMAIN} + http: + paths: + - backend: + service: + name: org1-peer2 + port: + name: grpc + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org1-peer2.${DOMAIN} \ No newline at end of file diff --git a/test-network-k8s/kube/org1/org1-tls-ca.yaml b/test-network-k8s/kube/org1/org1-tls-ca.yaml deleted file mode 100644 index a16da691..00000000 --- a/test-network-k8s/kube/org1/org1-tls-ca.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org1-tls-ca -spec: - replicas: 1 - selector: - matchLabels: - app: org1-tls-ca - template: - metadata: - labels: - app: org1-tls-ca - spec: - containers: - - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-ca:{{FABRIC_CA_VERSION}} - imagePullPolicy: IfNotPresent - env: - - name: FABRIC_CA_SERVER_CA_NAME - value: "org1-tls-ca" - - name: FABRIC_CA_SERVER_DEBUG - value: "false" - - name: FABRIC_CA_SERVER_HOME - value: "/var/hyperledger/fabric-tls-ca-server" - - 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-tls-ca-server/fabric-ca-server-config.yaml - subPath: fabric-tls-ca-server-config.yaml - readinessProbe: - tcpSocket: - port: 443 - initialDelaySeconds: 2 - periodSeconds: 5 - volumes: - - name: fabric-volume - persistentVolumeClaim: - claimName: fabric-org1 - - name: fabric-config - configMap: - name: org1-config - ---- -apiVersion: v1 -kind: Service -metadata: - name: org1-tls-ca -spec: - ports: - - name: tls - port: 443 - protocol: TCP - selector: - app: org1-tls-ca \ No newline at end of file diff --git a/test-network-k8s/kube/org1/org1-tls-cert-issuer.yaml b/test-network-k8s/kube/org1/org1-tls-cert-issuer.yaml new file mode 100644 index 00000000..863ec0df --- /dev/null +++ b/test-network-k8s/kube/org1/org1-tls-cert-issuer.yaml @@ -0,0 +1,32 @@ +# +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org1-tls-cert-issuer +spec: + isCA: true + privateKey: + algorithm: ECDSA + size: 256 + commonName: org1.example.com + secretName: org1-tls-cert-issuer-secret + issuerRef: + name: root-tls-cert-issuer + kind: Issuer + group: cert-manager.io + +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: org1-tls-cert-issuer +spec: + ca: + secretName: org1-tls-cert-issuer-secret + + diff --git a/test-network-k8s/kube/org2/org2-admin-cli.yaml b/test-network-k8s/kube/org2/org2-admin-cli.yaml deleted file mode 100644 index 6839455a..00000000 --- a/test-network-k8s/kube/org2/org2-admin-cli.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org2-admin-cli -spec: - replicas: 1 - selector: - matchLabels: - app: org2-admin-cli - template: - metadata: - labels: - app: org2-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 - - name: CORE_PEER_MSPCONFIGPATH - value: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp - - name: CORE_PEER_TLS_ROOTCERT_FILE - value: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/org2-tls-ca.pem - 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-org2 - - name: fabric-config - configMap: - name: org2-config - - name: msp-config - configMap: - name: msp-config diff --git a/test-network-k8s/kube/org2/org2-ca.yaml b/test-network-k8s/kube/org2/org2-ca.yaml new file mode 100644 index 00000000..59f7c000 --- /dev/null +++ b/test-network-k8s/kube/org2/org2-ca.yaml @@ -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: org2-ca-tls-cert +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org2-ca + - org2-ca.test-network.svc.cluster.local + - org2-ca.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org2-ca-tls-cert + issuerRef: + name: org2-tls-cert-issuer + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: org2-ca +spec: + replicas: 1 + selector: + matchLabels: + app: org2-ca + template: + metadata: + labels: + app: org2-ca + spec: + containers: + - name: main + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-ca:${FABRIC_CA_VERSION} + imagePullPolicy: IfNotPresent + env: + - name: FABRIC_CA_SERVER_CA_NAME + value: "org2-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-org2 + - name: fabric-config + configMap: + name: org2-config + - name: tls-cert-volume + secret: + secretName: org2-ca-tls-cert +--- +apiVersion: v1 +kind: Service +metadata: + name: org2-ca +spec: + ports: + - name: https + port: 443 + protocol: TCP + selector: + app: org2-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: org2-ca + name: org2-ca +spec: + ingressClassName: nginx + rules: + - host: org2-ca.${DOMAIN} + http: + paths: + - backend: + service: + name: org2-ca + port: + name: https + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org2-ca.${DOMAIN} + \ No newline at end of file diff --git a/test-network-k8s/kube/org2/org2-ecert-ca.yaml b/test-network-k8s/kube/org2/org2-ecert-ca.yaml deleted file mode 100644 index 216b53be..00000000 --- a/test-network-k8s/kube/org2/org2-ecert-ca.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org2-ecert-ca -spec: - replicas: 1 - selector: - matchLabels: - app: org2-ecert-ca - template: - metadata: - labels: - app: org2-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: "org2-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-org2 - - name: fabric-config - configMap: - name: org2-config - ---- -apiVersion: v1 -kind: Service -metadata: - name: org2-ecert-ca -spec: - ports: - - name: tls - port: 443 - protocol: TCP - selector: - app: org2-ecert-ca \ No newline at end of file diff --git a/test-network-k8s/kube/org2/org2-peer1.yaml b/test-network-k8s/kube/org2/org2-peer1.yaml index 112b98f1..89e3440b 100644 --- a/test-network-k8s/kube/org2/org2-peer1.yaml +++ b/test-network-k8s/kube/org2/org2-peer1.yaml @@ -3,6 +3,30 @@ # # SPDX-License-Identifier: Apache-2.0 # +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org2-peer1-tls-cert + namespace: test-network +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org2-peer1 + - org2-peer1.test-network.svc.cluster.local + - org2-peer1.${DOMAIN} + - org2-peer-gateway-svc + - org2-peer-gateway-svc.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org2-peer1-tls-cert + issuerRef: + name: org2-tls-cert-issuer + --- apiVersion: v1 kind: ConfigMap @@ -12,9 +36,9 @@ data: FABRIC_CFG_PATH: /var/hyperledger/fabric/config FABRIC_LOGGING_SPEC: "debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info" CORE_PEER_TLS_ENABLED: "true" - CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls/signcerts/cert.pem - CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls/keystore/server.key - CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls/cacerts/org2-tls-ca.pem + CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/config/tls/tls.crt + CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/config/tls/tls.key + CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/config/tls/ca.crt CORE_PEER_ID: org2-peer1.org2.example.com CORE_PEER_ADDRESS: org2-peer1:7051 CORE_PEER_LISTENADDRESS: 0.0.0.0:7051 @@ -29,6 +53,11 @@ data: CORE_PEER_FILESYSTEMPATH: /var/hyperledger/fabric/data/org2-peer1.org2.example.com CORE_LEDGER_SNAPSHOTS_ROOTDIR: /var/hyperledger/fabric/data/org2-peer1.org2.example.com/snapshots CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG: "{\"peername\":\"org2peer1\"}" + CORE_LEDGER_STATE_STATEDATABASE: CouchDB + CORE_LEDGER_STATE_COUCHDBCONFIG_MAXRETRIESONSTARTUP: "20" + CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS: localhost:5984 + CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME: admin + CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD: adminpw --- apiVersion: apps/v1 kind: Deployment @@ -47,7 +76,7 @@ spec: spec: containers: - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-peer:{{FABRIC_VERSION}} + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:${FABRIC_VERSION} imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -61,6 +90,19 @@ spec: mountPath: /var/hyperledger - name: fabric-config mountPath: /var/hyperledger/fabric/config + - name: tls-cert-volume + mountPath: /var/hyperledger/fabric/config/tls + readOnly: true + - name: couchdb + image: couchdb:3.2.1 + imagePullPolicy: IfNotPresent + env: + - name: "COUCHDB_USER" + value: "admin" + - name: "COUCHDB_PASSWORD" + value: "adminpw" + ports: + - containerPort: 5984 volumes: - name: fabric-volume persistentVolumeClaim: @@ -68,7 +110,9 @@ spec: - name: fabric-config configMap: name: org2-config - + - name: tls-cert-volume + secret: + secretName: org2-peer1-tls-cert --- apiVersion: v1 kind: Service @@ -76,7 +120,7 @@ metadata: name: org2-peer1 spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP - name: chaincode @@ -94,8 +138,46 @@ metadata: name: org2-peer-gateway-svc spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP selector: org: org2 +--- +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: org2-peer1 + name: org2-peer1 +spec: + ingressClassName: nginx + rules: + - host: org2-peer1.${DOMAIN} + http: + paths: + - backend: + service: + name: org2-peer1 + port: + name: grpc + path: / + pathType: ImplementationSpecific + - host: org2-peer-gateway-svc.${DOMAIN} + http: + paths: + - backend: + service: + name: org2-peer1 + port: + name: grpc + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org2-peer1.${DOMAIN} + - hosts: + - org2-peer-gateway-svc.${DOMAIN} diff --git a/test-network-k8s/kube/org2/org2-peer2.yaml b/test-network-k8s/kube/org2/org2-peer2.yaml index 2cd30175..268dde28 100644 --- a/test-network-k8s/kube/org2/org2-peer2.yaml +++ b/test-network-k8s/kube/org2/org2-peer2.yaml @@ -3,6 +3,29 @@ # # SPDX-License-Identifier: Apache-2.0 # +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org2-peer2-tls-cert + namespace: test-network +spec: + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - localhost + - org2-peer2 + - org2-peer-gateway-svc + - org2-peer2.test-network.svc.cluster.local + - org2-peer2.${DOMAIN} + ipAddresses: + - 127.0.0.1 + secretName: org2-peer2-tls-cert + issuerRef: + name: org2-tls-cert-issuer + --- apiVersion: v1 kind: ConfigMap @@ -12,9 +35,9 @@ data: FABRIC_CFG_PATH: /var/hyperledger/fabric/config FABRIC_LOGGING_SPEC: "debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info" CORE_PEER_TLS_ENABLED: "true" - CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls/signcerts/cert.pem - CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls/keystore/server.key - CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls/cacerts/org2-tls-ca.pem + CORE_PEER_TLS_CERT_FILE: /var/hyperledger/fabric/config/tls/tls.crt + CORE_PEER_TLS_KEY_FILE: /var/hyperledger/fabric/config/tls/tls.key + CORE_PEER_TLS_ROOTCERT_FILE: /var/hyperledger/fabric/config/tls/ca.crt CORE_PEER_ID: org2-peer2.org2.example.com CORE_PEER_ADDRESS: org2-peer2:7051 CORE_PEER_LISTENADDRESS: 0.0.0.0:7051 @@ -29,6 +52,11 @@ data: CORE_PEER_FILESYSTEMPATH: /var/hyperledger/fabric/data/org2-peer2.org2.example.com CORE_LEDGER_SNAPSHOTS_ROOTDIR: /var/hyperledger/fabric/data/org2-peer2.org2.example.com/snapshots CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG: "{\"peername\":\"org2peer2\"}" + CORE_LEDGER_STATE_STATEDATABASE: CouchDB + CORE_LEDGER_STATE_COUCHDBCONFIG_MAXRETRIESONSTARTUP: "20" + CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS: localhost:5984 + CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME: admin + CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD: adminpw --- apiVersion: apps/v1 kind: Deployment @@ -47,7 +75,7 @@ spec: spec: containers: - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-peer:{{FABRIC_VERSION}} + image: ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:${FABRIC_VERSION} imagePullPolicy: IfNotPresent envFrom: - configMapRef: @@ -61,7 +89,19 @@ spec: mountPath: /var/hyperledger - name: fabric-config mountPath: /var/hyperledger/fabric/config - + - name: tls-cert-volume + mountPath: /var/hyperledger/fabric/config/tls + readOnly: true + - name: couchdb + image: couchdb:3.2.1 + imagePullPolicy: IfNotPresent + env: + - name: "COUCHDB_USER" + value: "admin" + - name: "COUCHDB_PASSWORD" + value: "adminpw" + ports: + - containerPort: 5984 volumes: - name: fabric-volume persistentVolumeClaim: @@ -69,7 +109,9 @@ spec: - name: fabric-config configMap: name: org2-config - + - name: tls-cert-volume + secret: + secretName: org2-peer2-tls-cert --- apiVersion: v1 kind: Service @@ -77,7 +119,7 @@ metadata: name: org2-peer2 spec: ports: - - name: gossip + - name: grpc port: 7051 protocol: TCP - name: chaincode @@ -87,4 +129,30 @@ spec: port: 9443 protocol: TCP selector: - app: org2-peer2 \ No newline at end of file + app: org2-peer2 +--- +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: org2-peer2 + name: org2-peer2 +spec: + ingressClassName: nginx + rules: + - host: org2-peer2.${DOMAIN} + http: + paths: + - backend: + service: + name: org2-peer2 + port: + name: grpc + path: / + pathType: ImplementationSpecific + tls: + - hosts: + - org2-peer2.${DOMAIN} \ No newline at end of file diff --git a/test-network-k8s/kube/org2/org2-tls-ca.yaml b/test-network-k8s/kube/org2/org2-tls-ca.yaml deleted file mode 100644 index 53ec23db..00000000 --- a/test-network-k8s/kube/org2/org2-tls-ca.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: org2-tls-ca -spec: - replicas: 1 - selector: - matchLabels: - app: org2-tls-ca - template: - metadata: - labels: - app: org2-tls-ca - spec: - containers: - - name: main - image: {{FABRIC_CONTAINER_REGISTRY}}/fabric-ca:{{FABRIC_CA_VERSION}} - imagePullPolicy: IfNotPresent - env: - - name: FABRIC_CA_SERVER_CA_NAME - value: "org2-tls-ca" - - name: FABRIC_CA_SERVER_DEBUG - value: "false" - - name: FABRIC_CA_SERVER_HOME - value: "/var/hyperledger/fabric-tls-ca-server" - - 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-tls-ca-server/fabric-ca-server-config.yaml - subPath: fabric-tls-ca-server-config.yaml - readinessProbe: - tcpSocket: - port: 443 - initialDelaySeconds: 2 - periodSeconds: 5 - volumes: - - name: fabric-volume - persistentVolumeClaim: - claimName: fabric-org2 - - name: fabric-config - configMap: - name: org2-config - ---- -apiVersion: v1 -kind: Service -metadata: - name: org2-tls-ca -spec: - ports: - - name: tls - port: 443 - protocol: TCP - selector: - app: org2-tls-ca \ No newline at end of file diff --git a/test-network-k8s/kube/org2/org2-tls-cert-issuer.yaml b/test-network-k8s/kube/org2/org2-tls-cert-issuer.yaml new file mode 100644 index 00000000..86e45de1 --- /dev/null +++ b/test-network-k8s/kube/org2/org2-tls-cert-issuer.yaml @@ -0,0 +1,32 @@ +# +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: org2-tls-cert-issuer +spec: + isCA: true + privateKey: + algorithm: ECDSA + size: 256 + commonName: org2.example.com + secretName: org2-tls-cert-issuer-secret + issuerRef: + name: root-tls-cert-issuer + kind: Issuer + group: cert-manager.io + +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: org2-tls-cert-issuer +spec: + ca: + secretName: org2-tls-cert-issuer-secret + + diff --git a/test-network-k8s/kube/root-tls-cert-issuer.yaml b/test-network-k8s/kube/root-tls-cert-issuer.yaml new file mode 100644 index 00000000..21f94775 --- /dev/null +++ b/test-network-k8s/kube/root-tls-cert-issuer.yaml @@ -0,0 +1,12 @@ +# +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: root-tls-cert-issuer +spec: + selfSigned: {} diff --git a/test-network-k8s/network b/test-network-k8s/network index 05a9e0ac..955aef67 100755 --- a/test-network-k8s/network +++ b/test-network-k8s/network @@ -6,27 +6,25 @@ # set -o errexit -# todo: better handling for input parameters. +# todo: better handling for input parameters. Argbash? # todo: skip storage volume init if deploying to a remote / cloud cluster (ICP IKS ROKS etc...) # todo: for logging, set up a stack and allow multi-line status output codes -# todo: refactor - lots of for-org-in-0-to-2-... -# todo: find a better technique for passing input commands to a remote kube exec -# todo: register tls csr.hosts w/ kube DNS domain .NS.svc.cluster.local # todo: user:pass auth for tls and ecert bootstrap admins. here and in the server-config.yaml -# todo: set tls.certfiles= ... arg in deployment env / yaml # todo: refactor chaincode install to support other chaincode routines -# todo: consider using templates for boilerplate network nodes (orderers, peers, ...) # todo: allow the user to specify the chaincode name (hardcoded as 'basic') both in install and invoke/query # todo: track down a nasty bug whereby the CA service endpoints (kube services) will occasionally reject TCP connections after network down/up. This is patched by introducing a 10s sleep after the deployments are up... -# todo: refactor query/invoke to specify chaincode name (-n param) -FABRIC_VERSION=${TEST_NETWORK_FABRIC_VERSION:-2.4.1} -FABRIC_CA_VERSION=${TEST_NETWORK_FABRIC_CA_VERSION:-1.5.2} -FABRIC_CONTAINER_REGISTRY=${TEST_NETWORK_FABRIC_CONTAINER_REGISTRY:-hyperledger} -NETWORK_NAME=${TEST_NETWORK_NAME:-test-network} -CLUSTER_NAME=${TEST_NETWORK_KIND_CLUSTER_NAME:-kind} -NS=${TEST_NETWORK_KUBE_NAMESPACE:-${NETWORK_NAME}} -CHANNEL_NAME=${TEST_NETWORK_CHANNEL_NAME:-mychannel} +export CONTAINER_CLI=${CONTAINER_CLI:-docker} +export FABRIC_VERSION=${TEST_NETWORK_FABRIC_VERSION:-2.4.3} +export FABRIC_CA_VERSION=${TEST_NETWORK_FABRIC_CA_VERSION:-1.5.2} +export FABRIC_CONTAINER_REGISTRY=${TEST_NETWORK_FABRIC_CONTAINER_REGISTRY:-hyperledger} +export NETWORK_NAME=${TEST_NETWORK_NAME:-test-network} +export CLUSTER_NAME=${TEST_NETWORK_KIND_CLUSTER_NAME:-kind} +export NS=${TEST_NETWORK_KUBE_NAMESPACE:-${NETWORK_NAME}} +export DOMAIN=${TEST_NETWORK_DOMAIN:-vcap.me} +export CHANNEL_NAME=${TEST_NETWORK_CHANNEL_NAME:-mychannel} +export TEMP_DIR=${PWD}/build + LOG_FILE=${TEST_NETWORK_LOG_FILE:-network.log} DEBUG_FILE=${TEST_NETWORK_DEBUG_FILE:-network-debug.log} LOCAL_REGISTRY_NAME=${TEST_NETWORK_LOCAL_REGISTRY_NAME:-kind-registry} @@ -49,6 +47,7 @@ function print_help() { log "Fabric CA Version \t: ${FABRIC_CA_VERSION}" log "Container Registry \t: ${FABRIC_CONTAINER_REGISTRY}" log "Network name \t\t: ${NETWORK_NAME}" + log "Ingress domain \t\t: ${DOMAIN}" log "Channel name \t\t: ${CHANNEL_NAME}" log log "--- Chaincode Information" @@ -70,8 +69,6 @@ function print_help() { log "Debug log file \t\t: ${DEBUG_FILE}" log - - echo todo: help output, parse mode, flags, env, etc. } @@ -166,7 +163,7 @@ elif [ "${MODE}" == "chaincode" ]; then elif [ "${ACTION}" == "query" ]; then query_chaincode $@ >> ${LOG_FILE} - + elif [ "${ACTION}" == "metadata" ]; then query_chaincode_metadata >> ${LOG_FILE} else @@ -191,4 +188,3 @@ else print_help exit 1 fi - diff --git a/test-network-k8s/scripts/application_connection.sh b/test-network-k8s/scripts/application_connection.sh index 25d451a6..9f053ac0 100755 --- a/test-network-k8s/scripts/application_connection.sh +++ b/test-network-k8s/scripts/application_connection.sh @@ -8,11 +8,11 @@ function app_extract_MSP_archives() { mkdir -p build/msp set -ex - kubectl -n $NS exec deploy/org1-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/msp | tar zxf - -C build/msp - kubectl -n $NS exec deploy/org2-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/msp | tar zxf - -C build/msp + kubectl -n $NS exec deploy/org1-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/msp | tar zxf - -C build/msp + kubectl -n $NS exec deploy/org2-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/msp | tar zxf - -C build/msp - kubectl -n $NS exec deploy/org1-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp | tar zxf - -C build/msp - kubectl -n $NS exec deploy/org2-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp | tar zxf - -C build/msp + kubectl -n $NS exec deploy/org1-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp | tar zxf - -C build/msp + kubectl -n $NS exec deploy/org2-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp | tar zxf - -C build/msp } function app_one_line_pem { @@ -49,12 +49,12 @@ function construct_application_configmap() { mkdir -p build/application/gateways local peer_pem=build/msp/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/org1-tls-ca.pem - local ca_pem=build/msp/organizations/peerOrganizations/org1.example.com/msp/cacerts/org1-ecert-ca.pem + local ca_pem=build/msp/organizations/peerOrganizations/org1.example.com/msp/cacerts/org1-ca.pem echo "$(json_ccp 1 $peer_pem $ca_pem)" > build/application/gateways/org1_ccp.json peer_pem=build/msp/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/org2-tls-ca.pem - ca_pem=build/msp/organizations/peerOrganizations/org2.example.com/msp/cacerts/org2-ecert-ca.pem + ca_pem=build/msp/organizations/peerOrganizations/org2.example.com/msp/cacerts/org2-ca.pem echo "$(json_ccp 2 $peer_pem $ca_pem)" > build/application/gateways/org2_ccp.json diff --git a/test-network-k8s/scripts/ccp-template.json b/test-network-k8s/scripts/ccp-template.json index 4bd0a26e..c80f34b6 100755 --- a/test-network-k8s/scripts/ccp-template.json +++ b/test-network-k8s/scripts/ccp-template.json @@ -15,29 +15,29 @@ "Org${ORG}": { "mspid": "Org${ORG}MSP", "peers": [ - "org${ORG}-peer1" + "org${ORG}-peers" ], "certificateAuthorities": [ - "org${ORG}-ecert-ca" + "org${ORG}-ca" ] } }, "peers": { - "org${ORG}-peer1": { - "url": "grpcs://org${ORG}-peer1:7051", + "org${ORG}-peers": { + "url": "grpcs://org${ORG}-peer-gateway-svc:7051", "tlsCACerts": { "pem": "${PEERPEM}" }, "grpcOptions": { - "ssl-target-name-override": "org${ORG}-peer1", - "hostnameOverride": "org${ORG}-peer1" + "ssl-target-name-override": "org${ORG}-peer-gateway-svc", + "hostnameOverride": "org${ORG}-peer-gateway-svc" } } }, "certificateAuthorities": { - "org${ORG}-ecert-ca": { - "url": "https://org${ORG}-ecert-ca", - "caName": "org${ORG}-ecert-ca", + "org${ORG}-ca": { + "url": "https://org${ORG}-ca:443", + "caName": "org${ORG}-ca", "tlsCACerts": { "pem": ["${CAPEM}"] }, diff --git a/test-network-k8s/scripts/chaincode.sh b/test-network-k8s/scripts/chaincode.sh index 5eee57e7..e1275609 100755 --- a/test-network-k8s/scripts/chaincode.sh +++ b/test-network-k8s/scripts/chaincode.sh @@ -22,37 +22,23 @@ function package_chaincode_for() { pop_fn } -# Copy the chaincode archive from the local host to the org admin -function transfer_chaincode_archive_for() { - local org=$1 - local cc_archive="build/chaincode/${CHAINCODE_NAME}.tgz" - push_fn "Transferring chaincode archive to ${org}" - - # Like kubectl cp, but targeted to a deployment rather than an individual pod. - tar cf - ${cc_archive} | kubectl -n $NS exec -i deploy/${org}-admin-cli -c main -- tar xvf - - - pop_fn -} - function install_chaincode_for() { local org=$1 local peer=$2 - push_fn "Installing chaincode for org ${org} peer ${peer}" + push_fn "Installing chaincode for org ${org} peer ${peer}" - # Install the chaincode - echo 'set -x - export CORE_PEER_ADDRESS='${org}'-'${peer}':7051 - peer lifecycle chaincode install build/chaincode/'${CHAINCODE_NAME}'.tgz - ' | exec kubectl -n $NS exec deploy/${org}-admin-cli -c main -i -- /bin/bash + export_peer_context $org $peer + + peer lifecycle chaincode install build/chaincode/${CHAINCODE_NAME}.tgz pop_fn } function launch_chaincode_service() { local org=$1 - local cc_id=$2 - local cc_image=$3 - local peer=$4 + local peer=$2 + local cc_id=$3 + local cc_image=$4 push_fn "Launching chaincode container \"${cc_image}\"" # The chaincode endpoint needs to have the generated chaincode ID available in the environment. @@ -72,76 +58,72 @@ function launch_chaincode_service() { function activate_chaincode_for() { local org=$1 - local cc_id=$2 - push_fn "Activating chaincode ${CHAINCODE_ID}" + local peer=$2 + local cc_id=$3 + push_fn "Activating $org chaincode ${CHAINCODE_ID}" + + export_peer_context $org $peer - echo 'set -x - export CORE_PEER_ADDRESS='${org}'-peer1:7051 - peer lifecycle \ chaincode approveformyorg \ - --channelID '${CHANNEL_NAME}' \ - --name '${CHAINCODE_NAME}' \ - --version 1 \ - --package-id '${cc_id}' \ - --sequence 1 \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem - + --channelID ${CHANNEL_NAME} \ + --name ${CHAINCODE_NAME} \ + --version 1 \ + --package-id ${cc_id} \ + --sequence 1 \ + --orderer org0-orderer1.${DOMAIN}:443 \ + --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem + peer lifecycle \ chaincode commit \ - --channelID '${CHANNEL_NAME}' \ - --name '${CHAINCODE_NAME}' \ - --version 1 \ - --sequence 1 \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem - ' | exec kubectl -n $NS exec deploy/${org}-admin-cli -c main -i -- /bin/bash + --channelID ${CHANNEL_NAME} \ + --name ${CHAINCODE_NAME} \ + --version 1 \ + --sequence 1 \ + --orderer org0-orderer1.${DOMAIN}:443 \ + --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem pop_fn } function query_chaincode() { set -x - # todo: mangle additional $@ parameters with bash escape quotations - echo ' - export CORE_PEER_ADDRESS=org1-peer1:7051 - peer chaincode query -n '${CHAINCODE_NAME}' -C '${CHANNEL_NAME}' -c '"'$@'"' - ' | exec kubectl -n $NS exec deploy/org1-admin-cli -c main -i -- /bin/bash + + export_peer_context org1 peer1 + + peer chaincode query \ + -n $CHAINCODE_NAME \ + -C $CHANNEL_NAME \ + -c $@ } function query_chaincode_metadata() { set -x local args='{"Args":["org.hyperledger.fabric:GetMetadata"]}' - # todo: mangle additional $@ parameters with bash escape quotations + + log '' log 'Org1-Peer1:' - echo ' - export CORE_PEER_ADDRESS=org1-peer1:7051 - peer chaincode query -n '${CHAINCODE_NAME}' -C '${CHANNEL_NAME}' -c '"'$args'"' - ' | exec kubectl -n $NS exec deploy/org1-admin-cli -c main -i -- /bin/bash + export_peer_context org1 peer1 + peer chaincode query -n $CHAINCODE_NAME -C $CHANNEL_NAME -c $args log '' log 'Org1-Peer2:' - echo ' - export CORE_PEER_ADDRESS=org1-peer2:7051 - peer chaincode query -n '${CHAINCODE_NAME}' -C '${CHANNEL_NAME}' -c '"'$args'"' - ' | exec kubectl -n $NS exec deploy/org1-admin-cli -c main -i -- /bin/bash - + export_peer_context org1 peer2 + peer chaincode query -n $CHAINCODE_NAME -C $CHANNEL_NAME -c $args } function invoke_chaincode() { # set -x # todo: mangle additional $@ parameters with bash escape quotations - echo ' - export CORE_PEER_ADDRESS=org1-peer1:7051 - peer chaincode \ - invoke \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem \ - -n '${CHAINCODE_NAME}' \ - -C '${CHANNEL_NAME}' \ - -c '"'$@'"' - ' | exec kubectl -n $NS exec deploy/org1-admin-cli -c main -i -- /bin/bash + + export_peer_context org1 peer1 + + peer chaincode invoke \ + -n $CHAINCODE_NAME \ + -C $CHANNEL_NAME \ + -c $@ \ + --orderer org0-orderer1.${DOMAIN}:443 \ + --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem sleep 2 } @@ -162,7 +144,7 @@ function install_chaincode() { local org=org1 package_chaincode_for ${org} - transfer_chaincode_archive_for ${org} + install_chaincode_for ${org} peer1 install_chaincode_for ${org} peer2 @@ -174,7 +156,11 @@ function activate_chaincode() { set -x set_chaincode_id - activate_chaincode_for org1 $CHAINCODE_ID + activate_chaincode_for org1 peer1 $CHAINCODE_ID + + # jdk: does activation on a single peer apply to all peers in the org? This is an error: +# activate_chaincode_for org1 peer1 $CHAINCODE_ID + } # Install, launch, and activate the chaincode @@ -182,8 +168,8 @@ function deploy_chaincode() { set -x install_chaincode - launch_chaincode_service org1 $CHAINCODE_ID $CHAINCODE_IMAGE peer1 - launch_chaincode_service org1 $CHAINCODE_ID $CHAINCODE_IMAGE peer2 + launch_chaincode_service org1 peer1 $CHAINCODE_ID $CHAINCODE_IMAGE + launch_chaincode_service org1 peer2 $CHAINCODE_ID $CHAINCODE_IMAGE activate_chaincode } diff --git a/test-network-k8s/scripts/channel.sh b/test-network-k8s/scripts/channel.sh old mode 100755 new mode 100644 index 5aa3158f..d5d4e7c9 --- a/test-network-k8s/scripts/channel.sh +++ b/test-network-k8s/scripts/channel.sh @@ -5,197 +5,267 @@ # SPDX-License-Identifier: Apache-2.0 # -function create_channel_org_MSP() { +function channel_up() { + set -x + + register_org_admins + enroll_org_admins + + create_channel_MSP + create_genesis_block + + join_channel_orderers + join_channel_peers +} + +function register_org_admins() { + push_fn "Registering org Admin users" + + register_org_admin org0 org0admin org0adminpw + register_org_admin org1 org1admin org1adminpw + register_org_admin org2 org2admin org2adminpw + + pop_fn +} + +# Register the org admin user +function register_org_admin() { + local type=admin local org=$1 - local org_type=$2 - local ecert_ca=${org}-ecert-ca - - echo 'set -x - - mkdir -p /var/hyperledger/fabric/organizations/'${org_type}'Organizations/'${org}'.example.com/msp/cacerts - cp \ - $FABRIC_CA_CLIENT_HOME/'${ecert_ca}'/rcaadmin/msp/cacerts/'${ecert_ca}'.pem \ - /var/hyperledger/fabric/organizations/'${org_type}'Organizations/'${org}'.example.com/msp/cacerts - - mkdir -p /var/hyperledger/fabric/organizations/'${org_type}'Organizations/'${org}'.example.com/msp/tlscacerts - cp \ - $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp/cacerts/'${org}'-tls-ca.pem \ - /var/hyperledger/fabric/organizations/'${org_type}'Organizations/'${org}'.example.com/msp/tlscacerts - - echo "NodeOUs: - Enable: true - ClientOUIdentifier: - Certificate: cacerts/'${ecert_ca}'.pem - OrganizationalUnitIdentifier: client - PeerOUIdentifier: - Certificate: cacerts/'${ecert_ca}'.pem - OrganizationalUnitIdentifier: peer - AdminOUIdentifier: - Certificate: cacerts/'${ecert_ca}'.pem - OrganizationalUnitIdentifier: admin - OrdererOUIdentifier: - Certificate: cacerts/'${ecert_ca}'.pem - OrganizationalUnitIdentifier: orderer "> /var/hyperledger/fabric/organizations/'${org_type}'Organizations/'${org}'.example.com/msp/config.yaml - - ' | exec kubectl -n $NS exec deploy/${ecert_ca} -i -- /bin/sh + local id_name=$2 + local id_secret=$3 + local ca_name=${org}-ca + + echo "Registering org admin $username" + + cat < ${CA_DIR}/tls-cert.pem + + # enroll the org admin + FABRIC_CA_CLIENT_HOME=${ORG_ADMIN_DIR} fabric-ca-client enroll \ + --url ${CA_URL} \ + --tls.certfiles ${CA_DIR}/tls-cert.pem + + # Construct an msp config.yaml + CA_CERT_NAME=${CA_NAME}-$(echo $DOMAIN | tr -s . -)-${CA_PORT}.pem + + create_msp_config_yaml ${CA_NAME} ${CA_CERT_NAME} ${ORG_ADMIN_DIR}/msp + + # private keys are hashed by name, but we only support one enrollment. + # test-network examples refer to this as "server.key", which is incorrect. + # This is the private key used to endorse transactions using the admin's + # public key. + mv ${ORG_ADMIN_DIR}/msp/keystore/*_sk ${ORG_ADMIN_DIR}/msp/keystore/key.pem +} + +# create an enrollment MSP config.yaml +function create_msp_config_yaml() { + local ca_name=$1 + local ca_cert_name=$2 + local msp_dir=$3 + echo "Creating msp config ${msp_dir}/config.yaml with cert ${ca_cert_name}" + + cat << EOF > ${msp_dir}/config.yaml +NodeOUs: + Enable: true + ClientOUIdentifier: + Certificate: cacerts/${ca_cert_name} + OrganizationalUnitIdentifier: client + PeerOUIdentifier: + Certificate: cacerts/${ca_cert_name} + OrganizationalUnitIdentifier: peer + AdminOUIdentifier: + Certificate: cacerts/${ca_cert_name} + OrganizationalUnitIdentifier: admin + OrdererOUIdentifier: + Certificate: cacerts/${ca_cert_name} + OrganizationalUnitIdentifier: orderer +EOF } function create_channel_MSP() { push_fn "Creating channel MSP" - create_channel_org_MSP org0 orderer + create_channel_org_MSP org0 orderer create_channel_org_MSP org1 peer create_channel_org_MSP org2 peer - pop_fn -} - -function aggregate_channel_MSP() { - push_fn "Aggregating channel MSP" - - rm -rf ./build/msp/ - mkdir -p ./build/msp - - kubectl -n $NS exec deploy/org0-ecert-ca -- tar zcvf - -C /var/hyperledger/fabric organizations/ordererOrganizations/org0.example.com/msp > build/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 > build/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 > build/msp/msp-org2.example.com.tgz - - kubectl -n $NS delete configmap msp-config || true - kubectl -n $NS create configmap msp-config --from-file=build/msp/ + extract_orderer_tls_cert org0 orderer1 + extract_orderer_tls_cert org0 orderer2 + extract_orderer_tls_cert org0 orderer3 pop_fn } -function launch_admin_CLIs() { - push_fn "Launching admin CLIs" +function create_channel_org_MSP() { + local org=$1 + local type=$2 + local ca_name=${org}-ca - launch kube/org0/org0-admin-cli.yaml - launch kube/org1/org1-admin-cli.yaml - launch kube/org2/org2-admin-cli.yaml + ORG_MSP_DIR=${TEMP_DIR}/channel-msp/${type}Organizations/${org}/msp + mkdir -p ${ORG_MSP_DIR}/cacerts + mkdir -p ${ORG_MSP_DIR}/tlscacerts - kubectl -n $NS rollout status deploy/org0-admin-cli - kubectl -n $NS rollout status deploy/org1-admin-cli - kubectl -n $NS rollout status deploy/org2-admin-cli + # extract the CA's signing authority from the CA/cainfo response + curl -s \ + --cacert ${TEMP_DIR}/cas/${ca_name}/tls-cert.pem \ + https://${ca_name}.${DOMAIN}/cainfo \ + | jq -r .result.CAChain \ + | base64 -d \ + > ${ORG_MSP_DIR}/cacerts/ca-signcert.pem - pop_fn + # extract the CA's TLS CA certificate from the cert-manager secret + kubectl -n $NS get secret ${ca_name}-tls-cert -o json \ + | jq -r .data.\"ca.crt\" \ + | base64 -d \ + > ${ORG_MSP_DIR}/tlscacerts/tlsca-signcert.pem + + # create an MSP config.yaml with the CA's signing certificate + create_msp_config_yaml ${ca_name} ca-signcert.pem ${ORG_MSP_DIR} +} + +# Extract an orderer's TLS signing certificate for inclusion in the channel config block +function extract_orderer_tls_cert() { + local org=$1 + local orderer=$2 + + echo "Extracting TLS cert for $org $orderer" + + ORDERER_TLS_DIR=${TEMP_DIR}/channel-msp/ordererOrganizations/${org}/orderers/${org}-${orderer}/tls + mkdir -p $ORDERER_TLS_DIR/signcerts + + kubectl -n $NS get secret ${org}-${orderer}-tls-cert -o json \ + | jq -r .data.\"tls.crt\" \ + | base64 -d \ + > ${ORDERER_TLS_DIR}/signcerts/tls-cert.pem } function create_genesis_block() { - push_fn "Creating channel \"${CHANNEL_NAME}\"" + push_fn "Creating channel genesis block" - echo 'set -x - configtxgen -profile TwoOrgsApplicationGenesis -channelID '${CHANNEL_NAME}' -outputBlock genesis_block.pb - # configtxgen -inspectBlock genesis_block.pb - - osnadmin channel join --orderer-address org0-orderer1:9443 --channelID '${CHANNEL_NAME}' --config-block genesis_block.pb - osnadmin channel join --orderer-address org0-orderer2:9443 --channelID '${CHANNEL_NAME}' --config-block genesis_block.pb - osnadmin channel join --orderer-address org0-orderer3:9443 --channelID '${CHANNEL_NAME}' --config-block genesis_block.pb - - ' | exec kubectl -n $NS exec deploy/org0-admin-cli -i -- /bin/bash - - # todo: readiness / liveiness equivalent for channel ? Needs a little bit to settle before peers can join. + FABRIC_CFG_PATH=${PWD}/config/org0 \ + configtxgen \ + -profile TwoOrgsApplicationGenesis \ + -channelID $CHANNEL_NAME \ + -outputBlock ${TEMP_DIR}/genesis_block.pb + + # configtxgen -inspectBlock ${TEMP_DIR}/genesis_block.pb + + pop_fn +} + +function join_channel_orderers() { + push_fn "Joining orderers to channel ${CHANNEL_NAME}" + + join_channel_orderer org0 orderer1 + join_channel_orderer org0 orderer2 + join_channel_orderer org0 orderer3 + + # todo: readiness / liveiness equivalent for channel? Needs a little bit to settle before peers can join. sleep 10 pop_fn } -function join_org_peers() { +# Request from the channel ADMIN api that the orderer joins the target channel +function join_channel_orderer() { local org=$1 - push_fn "Joining ${org} peers to channel \"${CHANNEL_NAME}\"" + local orderer=$2 - echo 'set -x - # Fetch the genesis block from an orderer - peer channel \ - fetch oldest \ - genesis_block.pb \ - -c '${CHANNEL_NAME}' \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem - - # Join peer1 to the channel. - CORE_PEER_ADDRESS='${org}'-peer1:7051 \ - peer channel \ - join \ - -b genesis_block.pb \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem - - # Join peer2 to the channel. - CORE_PEER_ADDRESS='${org}'-peer2:7051 \ - peer channel \ - join \ - -b genesis_block.pb \ - -o org0-orderer1:6050 \ - --tls --cafile /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/msp/tlscacerts/org0-tls-ca.pem - - ' | exec kubectl -n $NS exec deploy/${org}-admin-cli -i -- /bin/bash - - pop_fn + # The client certificate presented in this case is the admin user's enrollment key. This is a stronger assertion + # of identity than the Docker Compose network, which transmits the orderer node's TLS key pair directly + osnadmin channel join \ + --orderer-address ${org}-${orderer}-admin.${DOMAIN} \ + --ca-file ${TEMP_DIR}/channel-msp/ordererOrganizations/${org}/orderers/${org}-${orderer}/tls/signcerts/tls-cert.pem \ + --client-cert ${TEMP_DIR}/enrollments/${org}/users/${org}admin/msp/signcerts/cert.pem \ + --client-key ${TEMP_DIR}/enrollments/${org}/users/${org}admin/msp/keystore/key.pem \ + --channelID ${CHANNEL_NAME} \ + --config-block ${TEMP_DIR}/genesis_block.pb } -function join_peers() { +function join_channel_peers() { join_org_peers org1 join_org_peers org2 } -# Copy the scripts/anchor_peers.sh to a remote volume -function push_anchor_peer_script() { +function join_org_peers() { local org=$1 + push_fn "Joining ${org} peers to channel ${CHANNEL_NAME}" - tar cf - scripts/ | kubectl -n $NS exec -i -c main deploy/${org}-admin-cli -- tar xf - -C /var/hyperledger/fabric -} - -verify_result() { - if [ $1 -ne 0 ]; then - echo $2 - exit $1 - fi -} - -# Launch the anchor peer update script on a remote org admin CLI -function invoke_anchor_peer_update() { - local org_num=$1 - local peer_name=$2 - - kubectl exec \ - -n $NS \ - -c main \ - deploy/org${org_num}-admin-cli \ - -i \ - /bin/bash -c "/var/hyperledger/fabric/scripts/set_anchor_peer.sh ${org_num} ${CHANNEL_NAME} ${peer_name}" - - verify_result $? "Error updating anchor peer for org ${org_num}" -} - -# -# To update the anchor peers we will need to execute a script on each of the peer admin CLI containers. These -# commands can be individually piped into kubectl exec ... but it will be simpler if we transfer the anchor -# peer update script over to the org volume and then trigger it from kubectl. -# -function update_anchor_peers() { - local peer_name=$1 - push_fn "Updating anchor peers to ${peer_name}" - - push_anchor_peer_script org1 - push_anchor_peer_script org2 - - invoke_anchor_peer_update 1 ${peer_name} - invoke_anchor_peer_update 2 ${peer_name} + # Join peers to channel + join_channel_peer $org peer1 + join_channel_peer $org peer2 pop_fn } -function channel_up() { +function join_channel_peer() { + local org=$1 + local peer=$2 - create_channel_MSP - aggregate_channel_MSP - launch_admin_CLIs + export_peer_context $org $peer - create_genesis_block - join_peers - - # peer1 was set as the anchor peer in configtx.yaml. Setting this again will force an - # error to be returned from the channel up. We might want to render the warning in - # this case to indicate that the call was made but had a nonzero exit. - # update_anchor_peers peer1 -} \ No newline at end of file + peer channel join \ + --blockpath ${TEMP_DIR}/genesis_block.pb \ + --orderer org0-orderer1.${DOMAIN} \ + --tls \ + --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem +} diff --git a/test-network-k8s/scripts/fabric_CAs.sh b/test-network-k8s/scripts/fabric_CAs.sh index a0ee760f..fd31b555 100755 --- a/test-network-k8s/scripts/fabric_CAs.sh +++ b/test-network-k8s/scripts/fabric_CAs.sh @@ -5,116 +5,40 @@ # SPDX-License-Identifier: Apache-2.0 # -function launch_CA() { - local yaml=$1 - cat ${yaml} \ - | sed 's,{{FABRIC_CONTAINER_REGISTRY}},'${FABRIC_CONTAINER_REGISTRY}',g' \ - | sed 's,{{FABRIC_CA_VERSION}},'${FABRIC_CA_VERSION}',g' \ - | kubectl -n $NS apply -f - -} - -function launch_TLS_CAs() { - push_fn "Launching TLS CAs" - - launch_CA kube/org0/org0-tls-ca.yaml - launch_CA kube/org1/org1-tls-ca.yaml - launch_CA kube/org2/org2-tls-ca.yaml - - kubectl -n $NS rollout status deploy/org0-tls-ca - kubectl -n $NS rollout status deploy/org1-tls-ca - kubectl -n $NS rollout status deploy/org2-tls-ca - - # todo: this papers over a nasty bug whereby the CAs are ready, but sporadically refuse connections after a down / up - sleep 10 - - pop_fn -} - function launch_ECert_CAs() { - push_fn "Launching ECert CAs" + push_fn "Launching Fabric CAs" - launch_CA kube/org0/org0-ecert-ca.yaml - launch_CA kube/org1/org1-ecert-ca.yaml - launch_CA kube/org2/org2-ecert-ca.yaml + apply_template kube/org0/org0-ca.yaml + apply_template kube/org1/org1-ca.yaml + apply_template kube/org2/org2-ca.yaml - kubectl -n $NS rollout status deploy/org0-ecert-ca - kubectl -n $NS rollout status deploy/org1-ecert-ca - kubectl -n $NS rollout status deploy/org2-ecert-ca + kubectl -n $NS rollout status deploy/org0-ca + kubectl -n $NS rollout status deploy/org1-ca + kubectl -n $NS rollout status deploy/org2-ca # todo: this papers over a nasty bug whereby the CAs are ready, but sporadically refuse connections after a down / up - sleep 10 + sleep 5 pop_fn } -# Enroll bootstrap user with TLS CA -# https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#enroll-bootstrap-user-with-tls-ca -function enroll_bootstrap_TLS_CA_user() { - local org=$1 - local auth=$2 - local tlsca=${org}-tls-ca +# experimental: create TLS CA issuers using cert-manager for each org. +function init_tls_cert_issuers() { + push_fn "Initializing TLS certificate Issuers" - # todo: get rid of export here - put in yaml + # Create a self-signing certificate issuer / root TLS certificate for the blockchain. + # TODO : Bring-Your-Own-Key - allow the network bootstrap to read an optional ECDSA key pair for the TLS trust root CA. + kubectl -n $NS apply -f kube/root-tls-cert-issuer.yaml + kubectl -n $NS wait --timeout=30s --for=condition=Ready issuer/root-tls-cert-issuer - echo 'set -x + # Use the self-signing issuer to generate three Issuers, one for each org. + kubectl -n $NS apply -f kube/org0/org0-tls-cert-issuer.yaml + kubectl -n $NS apply -f kube/org1/org1-tls-cert-issuer.yaml + kubectl -n $NS apply -f kube/org2/org2-tls-cert-issuer.yaml - mkdir -p $FABRIC_CA_CLIENT_HOME/tls-root-cert - cp $FABRIC_CA_SERVER_HOME/ca-cert.pem $FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem - - 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 - - ' | exec kubectl -n $NS exec deploy/${tlsca} -i -- /bin/sh -} - -function enroll_bootstrap_TLS_CA_users() { - push_fn "Enrolling bootstrap TLS CA users" - - enroll_bootstrap_TLS_CA_user org0 $TLSADMIN_AUTH - enroll_bootstrap_TLS_CA_user org1 $TLSADMIN_AUTH - enroll_bootstrap_TLS_CA_user org2 $TLSADMIN_AUTH - - pop_fn -} - -function register_enroll_ECert_CA_bootstrap_user() { - local org=$1 - local tlsauth=$2 - local tlsca=${org}-tls-ca - local ecertca=${org}-ecert-ca - - echo 'set -x - - 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 rcaadmin signing certificate is referenced by the ECert CA FABRIC_CA_SERVER_TLS_CERTFILE config attribute. - # For simplicity, reference the key at a fixed, known location - cp $FABRIC_CA_CLIENT_HOME/tls-ca/rcaadmin/msp/keystore/*_sk $FABRIC_CA_CLIENT_HOME/tls-ca/rcaadmin/msp/keystore/key.pem - - ' | exec kubectl -n $NS exec deploy/${tlsca} -i -- /bin/sh -} - -# https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/cadeploy.html#register-and-enroll-the-organization-ca-bootstrap-identity-with-the-tls-ca -function register_enroll_ECert_CA_bootstrap_users() { - push_fn "Registering and enrolling ECert CA bootstrap users" - - register_enroll_ECert_CA_bootstrap_user org0 $TLSADMIN_AUTH - register_enroll_ECert_CA_bootstrap_user org1 $TLSADMIN_AUTH - register_enroll_ECert_CA_bootstrap_user org2 $TLSADMIN_AUTH + kubectl -n $NS wait --timeout=30s --for=condition=Ready issuer/org0-tls-cert-issuer + kubectl -n $NS wait --timeout=30s --for=condition=Ready issuer/org1-tls-cert-issuer + kubectl -n $NS wait --timeout=30s --for=condition=Ready issuer/org2-tls-cert-issuer pop_fn } @@ -122,13 +46,13 @@ function register_enroll_ECert_CA_bootstrap_users() { function enroll_bootstrap_ECert_CA_user() { local org=$1 local auth=$2 - local ecert_ca=${org}-ecert-ca + local ecert_ca=${org}-ca echo 'set -x 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.crt \ --mspdir $FABRIC_CA_CLIENT_HOME/'${ecert_ca}'/rcaadmin/msp ' | exec kubectl -n $NS exec deploy/${ecert_ca} -i -- /bin/sh diff --git a/test-network-k8s/scripts/kind.sh b/test-network-k8s/scripts/kind.sh index 6d64d7d9..14cd6279 100755 --- a/test-network-k8s/scripts/kind.sh +++ b/test-network-k8s/scripts/kind.sh @@ -25,12 +25,13 @@ function load_docker_images() { kind load docker-image ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:$FABRIC_VERSION kind load docker-image ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION kind load docker-image ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest + kind load docker-image couchdb:3.2.1 pop_fn } function apply_nginx_ingress() { - push_fn "Launching Nginx ingress controller" + push_fn "Launching ingress controller" # This ingress-nginx.yaml was generated 9/24 from https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml # with modifications for ssl-passthrough required to launch IBP-support with the nginx ingress. @@ -41,9 +42,42 @@ function apply_nginx_ingress() { pop_fn } +function wait_for_nginx_ingress() { + push_fn "Waiting for ingress controller" + + kubectl wait --namespace ingress-nginx \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/component=controller \ + --timeout=90s + + pop_fn +} + +function apply_cert_manager() { + push_fn "Launching cert-manager" + + # Install cert-manager to manage TLS certificates + kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml + + pop_fn +} + +function wait_for_cert_manager() { + push_fn "Waiting for cert-manager" + + kubectl -n cert-manager rollout status deploy/cert-manager + kubectl -n cert-manager rollout status deploy/cert-manager-cainjector + kubectl -n cert-manager rollout status deploy/cert-manager-webhook + + pop_fn +} + function kind_create() { push_fn "Creating cluster \"${CLUSTER_NAME}\"" + # prevent the next kind cluster from using the previous Fabric network's enrollments. + rm -rf $PWD/build + # todo: always delete? Maybe return no-op if the cluster already exists? kind delete cluster --name $CLUSTER_NAME @@ -73,8 +107,8 @@ nodes: - containerPort: 443 hostPort: ${ingress_https_port} protocol: TCP -networking: - kubeProxyMode: "ipvs" +#networking: +# kubeProxyMode: "ipvs" # create a cluster with the local registry enabled in containerd containerdConfigPatches: @@ -84,6 +118,12 @@ containerdConfigPatches: EOF + # workaround for https://github.com/hyperledger/fabric-samples/issues/550 - pods can not resolve external DNS + for node in $(kind get nodes); + do + docker exec "$node" sysctl net.ipv4.conf.all.route_localnet=1; + done + pop_fn } @@ -137,12 +177,16 @@ function kind_init() { kind_create apply_nginx_ingress + apply_cert_manager launch_docker_registry if [ "${STAGE_DOCKER_IMAGES}" == true ]; then pull_docker_images load_docker_images - fi + fi + + wait_for_cert_manager + wait_for_nginx_ingress } function kind_unkind() { diff --git a/test-network-k8s/scripts/prereqs.sh b/test-network-k8s/scripts/prereqs.sh index 0c6ae815..7fa00262 100755 --- a/test-network-k8s/scripts/prereqs.sh +++ b/test-network-k8s/scripts/prereqs.sh @@ -8,9 +8,11 @@ # Double check that kind, kubectl, docker, and all required images are present. function check_prereqs() { - docker version > /dev/null + set +e + + ${CONTAINER_CLI} version > /dev/null if [[ $? -ne 0 ]]; then - echo "No 'docker' binary available? (https://www.docker.com)" + echo "No '${CONTAINER_CLI}' binary available?" exit 1 fi @@ -31,4 +33,27 @@ function check_prereqs() { echo "No 'jq' binary available? (https://stedolan.github.io/jq/)" exit 1 fi + + # Use the local fabric binaries if available. If not, go get them. + bin/peer version &> /dev/null + if [[ $? -ne 0 ]]; then + echo "Downloading LATEST Fabric binaries and config" + curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/bootstrap.sh | bash -s -- -s -d + + # remove sample config files extracted by the installation script + rm config/configtx.yaml + rm config/core.yaml + rm config/orderer.yaml + fi + + export PATH=bin:$PATH + + # Double-check that the binary transfer was OK + peer version > /dev/null + if [[ $? -ne 0 ]]; then + log "No 'peer' binary available?" + exit 1 + fi + + set -e } \ No newline at end of file diff --git a/test-network-k8s/scripts/rest_sample.sh b/test-network-k8s/scripts/rest_sample.sh index fe397164..c40c6b78 100755 --- a/test-network-k8s/scripts/rest_sample.sh +++ b/test-network-k8s/scripts/rest_sample.sh @@ -5,16 +5,8 @@ # SPDX-License-Identifier: Apache-2.0 # -function extract_MSP_archives() { - mkdir -p build/msp - - kubectl -n $NS exec deploy/org1-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/msp | tar zxf - -C build/msp - kubectl -n $NS exec deploy/org2-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/msp | tar zxf - -C build/msp - - kubectl -n $NS exec deploy/org1-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp | tar zxf - -C build/msp - kubectl -n $NS exec deploy/org2-ecert-ca -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp | tar zxf - -C build/msp -} - +# This magical awk script led to 30 hours of debugging a "TLS handshake error" +# moral: do not edit / alter the number of '\' in the following transform: function one_line_pem { echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`" } @@ -32,41 +24,32 @@ function json_ccp { function construct_rest_sample_configmap() { push_fn "Constructing fabric-rest-sample connection profiles" - extract_MSP_archives + ENROLLMENT_DIR=${TEMP_DIR}/enrollments + CHANNEL_MSP_DIR=${TEMP_DIR}/channel-msp + CONFIG_DIR=${TEMP_DIR}/fabric-rest-sample-config - mkdir -p build/fabric-rest-sample-config - - local peer_pem=build/msp/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/org1-tls-ca.pem - local ca_pem=build/msp/organizations/peerOrganizations/org1.example.com/msp/cacerts/org1-ecert-ca.pem + mkdir -p $CONFIG_DIR + local peer_pem=$CHANNEL_MSP_DIR/peerOrganizations/org1/msp/tlscacerts/tlsca-signcert.pem + local ca_pem=$CHANNEL_MSP_DIR/peerOrganizations/org1/msp/cacerts/ca-signcert.pem echo "$(json_ccp 1 $peer_pem $ca_pem)" > build/fabric-rest-sample-config/HLF_CONNECTION_PROFILE_ORG1 - - peer_pem=build/msp/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/org2-tls-ca.pem - ca_pem=build/msp/organizations/peerOrganizations/org2.example.com/msp/cacerts/org2-ecert-ca.pem + peer_pem=$CHANNEL_MSP_DIR/peerOrganizations/org2/msp/tlscacerts/tlsca-signcert.pem + ca_pem=$CHANNEL_MSP_DIR/peerOrganizations/org2/msp/cacerts/ca-signcert.pem echo "$(json_ccp 2 $peer_pem $ca_pem)" > build/fabric-rest-sample-config/HLF_CONNECTION_PROFILE_ORG2 - - cat build/msp/organizations/peerOrganizations/org1.example.com/users/Admin\@org1.example.com/msp/signcerts/cert.pem > build/fabric-rest-sample-config/HLF_CERTIFICATE_ORG1 - cat build/msp/organizations/peerOrganizations/org2.example.com/users/Admin\@org2.example.com/msp/signcerts/cert.pem > build/fabric-rest-sample-config/HLF_CERTIFICATE_ORG2 - cat build/msp/organizations/peerOrganizations/org1.example.com/users/Admin\@org1.example.com/msp/keystore/server.key > build/fabric-rest-sample-config/HLF_PRIVATE_KEY_ORG1 - cat build/msp/organizations/peerOrganizations/org2.example.com/users/Admin\@org2.example.com/msp/keystore/server.key > build/fabric-rest-sample-config/HLF_PRIVATE_KEY_ORG2 + cp $ENROLLMENT_DIR/org1/users/org1admin/msp/signcerts/cert.pem $CONFIG_DIR/HLF_CERTIFICATE_ORG1 + cp $ENROLLMENT_DIR/org2/users/org2admin/msp/signcerts/cert.pem $CONFIG_DIR/HLF_CERTIFICATE_ORG2 + + cp $ENROLLMENT_DIR/org1/users/org1admin/msp/keystore/key.pem $CONFIG_DIR/HLF_PRIVATE_KEY_ORG1 + cp $ENROLLMENT_DIR/org2/users/org2admin/msp/keystore/key.pem $CONFIG_DIR/HLF_PRIVATE_KEY_ORG2 kubectl -n $NS delete configmap fabric-rest-sample-config || true - kubectl -n $NS create configmap fabric-rest-sample-config --from-file=build/fabric-rest-sample-config/ + kubectl -n $NS create configmap fabric-rest-sample-config --from-file=$CONFIG_DIR pop_fn } -# todo: Make sure to port this to IKS / ICP -function ensure_rest_sample_image() { - push_fn "Ensuring fabric-rest-sample image" - - # todo: apply a tag / label to avoid pulling :latest from ghcr.io - - pop_fn 0 -} - function rollout_rest_sample() { push_fn "Starting fabric-rest-sample" @@ -77,15 +60,19 @@ function rollout_rest_sample() { } function launch_rest_sample() { - ensure_rest_sample_image + construct_rest_sample_configmap - rollout_rest_sample + + apply_template kube/fabric-rest-sample.yaml + + kubectl -n $NS rollout status deploy/fabric-rest-sample log "" - log "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." + log "The fabric-rest-sample has started." + log "See https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/rest-api-typescript for additional usage details." log "To access the endpoint:" log "" log "export SAMPLE_APIKEY=97834158-3224-4CE7-95F9-A148C886653E" - log 'curl -s --header "X-Api-Key: ${SAMPLE_APIKEY}" http://localhost/api/assets' + log 'curl -s --header "X-Api-Key: ${SAMPLE_APIKEY}" http://fabric-rest-sample.'${DOMAIN}'/api/assets' log "" } \ No newline at end of file diff --git a/test-network-k8s/scripts/test_network.sh b/test-network-k8s/scripts/test_network.sh index e938ba00..c8965db8 100755 --- a/test-network-k8s/scripts/test_network.sh +++ b/test-network-k8s/scripts/test_network.sh @@ -5,23 +5,12 @@ # SPDX-License-Identifier: Apache-2.0 # -# todo: oof this is rough. - - -function launch() { - local yaml=$1 - cat ${yaml} \ - | sed 's,{{FABRIC_CONTAINER_REGISTRY}},'${FABRIC_CONTAINER_REGISTRY}',g' \ - | sed 's,{{FABRIC_VERSION}},'${FABRIC_VERSION}',g' \ - | kubectl -n $NS apply -f - -} - function launch_orderers() { push_fn "Launching orderers" - launch kube/org0/org0-orderer1.yaml - launch kube/org0/org0-orderer2.yaml - launch kube/org0/org0-orderer3.yaml + apply_template kube/org0/org0-orderer1.yaml + apply_template kube/org0/org0-orderer2.yaml + apply_template kube/org0/org0-orderer3.yaml kubectl -n $NS rollout status deploy/org0-orderer1 kubectl -n $NS rollout status deploy/org0-orderer2 @@ -33,10 +22,10 @@ function launch_orderers() { function launch_peers() { push_fn "Launching peers" - launch kube/org1/org1-peer1.yaml - launch kube/org1/org1-peer2.yaml - launch kube/org2/org2-peer1.yaml - launch kube/org2/org2-peer2.yaml + apply_template kube/org1/org1-peer1.yaml + apply_template kube/org1/org1-peer2.yaml + apply_template kube/org2/org2-peer1.yaml + apply_template kube/org2/org2-peer2.yaml kubectl -n $NS rollout status deploy/org1-peer1 kubectl -n $NS rollout status deploy/org1-peer2 @@ -46,165 +35,127 @@ function launch_peers() { pop_fn } -function create_org0_local_MSP() { - echo 'set -x +# todo: enroll org admin LOCALLY from the host OS +# fabric-ca-client register --id.name org1-admin --id.secret org1adminpw --id.type admin --url https://org1-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-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://org1-admin:org1adminpw@org1-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/server.key +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml + +# fabric-ca-client register --id.name org1-admin --id.secret org1adminpw --id.type admin --url https://org1-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-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://org1-admin:org1adminpw@org1-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/server.key +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml + +# fabric-ca-client register --id.name org2-admin --id.secret org2adminpw --id.type admin --url https://org2-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-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://org2-admin:org2adminpw@org2-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/server.key +# cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/config.yaml + + +# Each network node needs a registration, enrollment, and MSP config.yaml +function create_node_local_MSP() { + local node_type=$1 + local org=$2 + local node=$3 + local csr_hosts=$4 + local id_name=${org}-${node} + local id_secret=${node_type}pw + local ca_name=${org}-ca + + cat < /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml - - cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer2.org0.example.com/msp/config.yaml - cp /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer1.org0.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/org0-orderer3.org0.example.com/msp/config.yaml - ' | exec kubectl -n $NS exec deploy/org0-ecert-ca -i -- /bin/sh -} - -function create_org1_local_MSP() { - - echo 'set -x - export FABRIC_CA_CLIENT_HOME=/var/hyperledger/fabric-ca-client - export FABRIC_CA_CLIENT_TLS_CERTFILES=$FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem - - # Each identity in the network needs a registration and enrollment. - fabric-ca-client register --id.name org1-peer1 --id.secret peerpw --id.type peer --url https://org1-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-ecert-ca/rcaadmin/msp - fabric-ca-client register --id.name org1-peer2 --id.secret peerpw --id.type peer --url https://org1-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-ecert-ca/rcaadmin/msp - fabric-ca-client register --id.name org1-admin --id.secret org1adminpw --id.type admin --url https://org1-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-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 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-peer2:peerpw@org1-ecert-ca --csr.hosts org1-peer2,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/msp - fabric-ca-client enroll --url https://org1-admin:org1adminpw@org1-ecert-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp - - # Each node in the network needs a TLS registration and enrollment. - fabric-ca-client register --id.name org1-peer1 --id.secret peerpw --id.type peer --url https://org1-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp - fabric-ca-client register --id.name org1-peer2 --id.secret peerpw --id.type peer --url https://org1-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp - - fabric-ca-client enroll --url https://org1-peer1:peerpw@org1-tls-ca --csr.hosts org1-peer1 --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls - fabric-ca-client enroll --url https://org1-peer2:peerpw@org1-tls-ca --csr.hosts org1-peer2 --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls - - # Copy the TLS signing keys to a fixed path for convenience when launching the peers - cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/tls/keystore/server.key - cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/tls/keystore/server.key - - cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/server.key + fabric-ca-client enroll \ + --url https://${id_name}:${id_secret}@${ca_name} \ + --csr.hosts ${csr_hosts} \ + --mspdir /var/hyperledger/fabric/organizations/${node_type}Organizations/${org}.example.com/${node_type}s/${id_name}.${org}.example.com/msp # Create local MSP config.yaml echo "NodeOUs: Enable: true ClientOUIdentifier: - Certificate: cacerts/org1-ecert-ca.pem + Certificate: cacerts/${org}-ca.pem OrganizationalUnitIdentifier: client PeerOUIdentifier: - Certificate: cacerts/org1-ecert-ca.pem + Certificate: cacerts/${org}-ca.pem OrganizationalUnitIdentifier: peer AdminOUIdentifier: - Certificate: cacerts/org1-ecert-ca.pem + Certificate: cacerts/${org}-ca.pem OrganizationalUnitIdentifier: admin OrdererOUIdentifier: - Certificate: cacerts/org1-ecert-ca.pem - OrganizationalUnitIdentifier: orderer" > /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp/config.yaml - - - cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/msp/config.yaml - cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml - ' | exec kubectl -n $NS exec deploy/org1-ecert-ca -i -- /bin/sh - + Certificate: cacerts/${org}-ca.pem + OrganizationalUnitIdentifier: orderer" > /var/hyperledger/fabric/organizations/${node_type}Organizations/${org}.example.com/${node_type}s/${id_name}.${org}.example.com/msp/config.yaml +EOF } -function create_org2_local_MSP() { - echo 'set -x - export FABRIC_CA_CLIENT_HOME=/var/hyperledger/fabric-ca-client - export FABRIC_CA_CLIENT_TLS_CERTFILES=$FABRIC_CA_CLIENT_HOME/tls-root-cert/tls-ca-cert.pem +function create_orderer_local_MSP() { + local org=$1 + local orderer=$2 + local csr_hosts=${org}-${orderer} - # Each identity in the network needs a registration and enrollment. - fabric-ca-client register --id.name org2-peer1 --id.secret peerpw --id.type peer --url https://org2-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-ecert-ca/rcaadmin/msp - fabric-ca-client register --id.name org2-peer2 --id.secret peerpw --id.type peer --url https://org2-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-ecert-ca/rcaadmin/msp - fabric-ca-client register --id.name org2-admin --id.secret org2adminpw --id.type admin --url https://org2-ecert-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-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" + create_node_local_MSP orderer $org $orderer $csr_hosts +} - fabric-ca-client enroll --url https://org2-peer1:peerpw@org2-ecert-ca --csr.hosts org2-peer1,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp - fabric-ca-client enroll --url https://org2-peer2:peerpw@org2-ecert-ca --csr.hosts org2-peer2,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/msp - fabric-ca-client enroll --url https://org2-admin:org2adminpw@org2-ecert-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp +function create_peer_local_MSP() { + local org=$1 + local peer=$2 + local csr_hosts=localhost,${org}-${peer},${org}-peer-gateway-svc - # Each node in the network needs a TLS registration and enrollment. - fabric-ca-client register --id.name org2-peer1 --id.secret peerpw --id.type peer --url https://org2-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp - fabric-ca-client register --id.name org2-peer2 --id.secret peerpw --id.type peer --url https://org2-tls-ca --mspdir $FABRIC_CA_CLIENT_HOME/tls-ca/tlsadmin/msp - - fabric-ca-client enroll --url https://org2-peer1:peerpw@org2-tls-ca --csr.hosts org2-peer1 --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls - fabric-ca-client enroll --url https://org2-peer2:peerpw@org2-tls-ca --csr.hosts org2-peer2 --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls - - # Copy the TLS signing keys to a fixed path for convenience when launching the peers - cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/tls/keystore/server.key - cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/tls/keystore/server.key - - cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/server.key - - # Create local MSP config.yaml - echo "NodeOUs: - Enable: true - ClientOUIdentifier: - Certificate: cacerts/org2-ecert-ca.pem - OrganizationalUnitIdentifier: client - PeerOUIdentifier: - Certificate: cacerts/org2-ecert-ca.pem - OrganizationalUnitIdentifier: peer - AdminOUIdentifier: - Certificate: cacerts/org2-ecert-ca.pem - OrganizationalUnitIdentifier: admin - OrdererOUIdentifier: - Certificate: cacerts/org2-ecert-ca.pem - OrganizationalUnitIdentifier: orderer" > /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp/config.yaml - - cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/msp/config.yaml - cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp/config.yaml /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/config.yaml - ' | exec kubectl -n $NS exec deploy/org2-ecert-ca -i -- /bin/sh + create_node_local_MSP peer $org $peer $csr_hosts } function create_local_MSP() { push_fn "Creating local node MSP" - create_org0_local_MSP - create_org1_local_MSP - create_org2_local_MSP + create_orderer_local_MSP org0 orderer1 + create_orderer_local_MSP org0 orderer2 + create_orderer_local_MSP org0 orderer3 + + create_peer_local_MSP org1 peer1 + create_peer_local_MSP org1 peer2 + + create_peer_local_MSP org2 peer1 + create_peer_local_MSP org2 peer2 pop_fn } +# +## TLS certificates are isused by the CA's Issuer, stored in a Kube secret, and mounted into the pod at /var/hyperledger/fabric/config/tls. +## For consistency with the Fabric-CA guide, his function copies the orderer's TLS certs into the traditional Fabric MSP / folder structure. +#function extract_orderer_tls_cert() { +# local orderer=$1 +# +# echo 'set -x +# +# mkdir -p /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/'${orderer}'.org0.example.com/tls/signcerts/ +# +# cp \ +# var/hyperledger/fabric/config/tls/tls.crt \ +# /var/hyperledger/fabric/organizations/ordererOrganizations/org0.example.com/orderers/'${orderer}'.org0.example.com/tls/signcerts/cert.pem +# +# ' | exec kubectl -n $NS exec deploy/${orderer} -i -c main -- /bin/sh +#} +# +#function extract_orderer_tls_certs() { +# push_fn "Extracting orderer TLS certs to local MSP folder" +# +# extract_orderer_tls_cert org0-orderer1 +# extract_orderer_tls_cert org0-orderer2 +# extract_orderer_tls_cert org0-orderer3 +# +# pop_fn +#} function network_up() { @@ -214,32 +165,31 @@ function network_up() { load_org_config # Network TLS CAs - launch_TLS_CAs - enroll_bootstrap_TLS_CA_users + init_tls_cert_issuers # Network ECert CAs - register_enroll_ECert_CA_bootstrap_users launch_ECert_CAs enroll_bootstrap_ECert_CA_users # Test Network create_local_MSP + launch_orderers launch_peers + +# extract_orderer_tls_certs } function stop_services() { push_fn "Stopping Fabric services" - # These pods are busy executing `sleep MAX_INT` and do not shut down very quickly... -# kubectl -n $NS delete deployment/org0-admin-cli --grace-period=0 --force -# kubectl -n $NS delete deployment/org1-admin-cli --grace-period=0 --force -# kubectl -n $NS delete deployment/org2-admin-cli --grace-period=0 --force - + kubectl -n $NS delete ingress --all kubectl -n $NS delete deployment --all kubectl -n $NS delete pod --all kubectl -n $NS delete service --all kubectl -n $NS delete configmap --all + kubectl -n $NS delete cert --all + kubectl -n $NS delete issuer --all kubectl -n $NS delete secret --all pop_fn @@ -262,4 +212,6 @@ function scrub_org_volumes() { function network_down() { stop_services scrub_org_volumes -} \ No newline at end of file + + rm -rf $PWD/build +} diff --git a/test-network-k8s/scripts/utils.sh b/test-network-k8s/scripts/utils.sh index 14f4697b..0b97c07d 100644 --- a/test-network-k8s/scripts/utils.sh +++ b/test-network-k8s/scripts/utils.sh @@ -25,6 +25,8 @@ function logging_init() { function exit_fn() { rc=$? + set +x + # Write an error icon to the current logging statement. if [ "0" -ne $rc ]; then pop_fn $rc @@ -73,3 +75,29 @@ function pop_fn() { echo "" >> ${LOG_FILE} } +# Apply the current environment to a k8s template and apply to the cluster. +function apply_template() { + + echo "Applying template $1:" + cat $1 | envsubst + + cat $1 | envsubst | kubectl -n $NS apply -f - +} + +# Set the calling context to refer the peer binary to the correct org / peer instance +# +# todo: Expose the output of this function to a target that prints the context to STDOUT. +# +# e.g.: +# bash $ source $(network set-peer-context org1 peer2) +# bash $ peer chaincode list +# bash $ ... +function export_peer_context() { + local org=$1 + local peer=$2 + + export FABRIC_CFG_PATH=${PWD}/config/${org} + export CORE_PEER_ADDRESS=${org}-${peer}.${DOMAIN}:443 + export CORE_PEER_MSPCONFIGPATH=${TEMP_DIR}/enrollments/${org}/users/${org}admin/msp + export CORE_PEER_TLS_ROOTCERT_FILE=${TEMP_DIR}/channel-msp/peerOrganizations/${org}/msp/tlscacerts/tlsca-signcert.pem +} diff --git a/test-network-nano-bash/README.md b/test-network-nano-bash/README.md index 4e077de4..44363f3e 100644 --- a/test-network-nano-bash/README.md +++ b/test-network-nano-bash/README.md @@ -2,7 +2,7 @@ Test network Nano bash provides a set of minimal bash scripts to run a Fabric network on your local machine. The network is functionally equivalent to the docker-based Test Network, you can therefore run all the tutorials and samples that target the Test Network with minimal changes. -The Fabric release binaries are utilized rather than using docker containers to avoid all unnecessary layers. Only the chaincode and chaincode builder runs in a docker container behind the scenes. +The Fabric release binaries are utilized rather than using docker containers to avoid all unnecessary layers. And you can choose between running the chaincode and chaincode builder in a docker container behind the scenes or running the chaincode as a service without any containers at all. Using the Fabric binaries also makes it simple for Fabric developers to iteratively and quickly modify Fabric code and test a Fabric network as a user. As the name `nano` implies, the scripts provide the smallest minimal setup possible for a Fabric network while still offering a multi-node TLS-enabled network: @@ -18,6 +18,20 @@ As the name `nano` implies, the scripts provide the smallest minimal setup possi - Follow the Fabric documentation for the [Prereqs](https://hyperledger-fabric.readthedocs.io/en/latest/prereqs.html) - Follow the Fabric documentation for [downloading the Fabric samples and binaries](https://hyperledger-fabric.readthedocs.io/en/latest/install.html). You can skip the docker image downloads by using `curl -sSL https://bit.ly/2ysbOFE | bash -s -- -d` +## To run the chaincode as a service +- You need to have the `ccaas_builder` binaries. If you do not have them in `fabric-samples/bin` you can build them from the Fabric source with the command `make ccaasbuilder`, you will then find the builder in `fabric/release/darwin-amd64/bin` or equivalent for your system. Just move the whole hierarchy starting there to `fabric-samples/bin` with something like: `mv release/darwin-amd64/bin/ccaas_builder ../fabric-samples/bin` +- You need to edit the `fabric-samples/config/core.yaml` file to point to that builder. The path specified in the default config file is only valid within the peer container which you won't be using. Modify the `externalBuilders` field in the `core.yaml` file to add the local external builder so that the configuration looks something like the following: +``` +externalBuilders: + - name: ccaas_builder + path: /opt/hyperledger/ccaas_builder + propagateEnvironment: + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG + - name: external-sample-builder + path: ../bin/ccaas_builder +``` +The path must be absolute or relative to where the peer will run so that it can find the builder when installing the chaincode. + # Instructions for starting network Open terminal windows for 3 ordering nodes, 4 peer nodes, and 4 peer admins as seen in the following terminal setup. The first two peers and peer admins belong to Org1, the latter two peer and peer admins belong to Org2. @@ -42,7 +56,12 @@ The remaining peer admin scripts join their respective peers to `mychannel`. # Instructions for deploying and running the basic asset transfer sample chaincode -To deploy and invoke the chaincode, utilize the peer1 admin terminal that you have created in the prior steps. +To deploy and invoke the chaincode, utilize the peer1 admin terminal that you have created in the prior steps. You have two possibilities: + +1. Using a chaincode container +2. Running the chaincode as a service + +## 1. Using a chaincode container Package and install the chaincode on peer1: @@ -52,23 +71,72 @@ peer lifecycle chaincode package basic.tar.gz --path ../asset-transfer-basic/cha peer lifecycle chaincode install basic.tar.gz ``` -The chaincode install may take a minute since the `fabric-ccenv` chaincode builder docker image will be downloaded if not already available on your machine. +The chaincode install may take a minute since the `fabric-ccenv` chaincode builder docker image will be downloaded if not already available on your machine. Copy the returned chaincode package ID into an environment variable for use in subsequent commands (your ID may be different): + +``` +export CHAINCODE_ID=basic_1:faaa38f2fc913c8344986a7d1617d21f6c97bc8d85ee0a489c90020cd57af4a5 +``` + + +## 2. Running the chaincode as a service + +Package and install the external chaincode on peer1 with the following simple commands: + +``` +cd chaincode-external + +tar cfz code.tar.gz connection.json +tar cfz external-chaincode.tgz metadata.json code.tar.gz + +cd .. + +peer lifecycle chaincode install chaincode-external/external-chaincode.tgz +``` + Copy the returned chaincode package ID into an environment variable for use in subsequent commands (your ID may be different): ``` -export CC_PACKAGE_ID=basic_1:faaa38f2fc913c8344986a7d1617d21f6c97bc8d85ee0a489c90020cd57af4a5 +export CHAINCODE_ID=basic_1.0:f3e2ca5115bba71aa2fd16e35722b420cb29c42594f0fdd6814daedbc2130b80 ``` -Approve and commit the chaincode (only a single approver is required based on the lifecycle endorsement policy of any organization): +In another terminal, navigate to `fabric-samples/asset-transfer-basic/chaincode-typescript` and build the chaincode: ``` -peer lifecycle chaincode approveformyorg -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${PWD}/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt +npm install +``` + +Set the chaincode package ID again (this is a different terminal): + +``` +export CHAINCODE_ID=basic_1.0:f3e2ca5115bba71aa2fd16e35722b420cb29c42594f0fdd6814daedbc2130b80 +``` + +Set the chaincode server address: + +``` +export CHAINCODE_SERVER_ADDRESS=127.0.0.1:9999 +``` + +And start the chaincode service: + +``` +npm run start:server-notls +``` + +## Activate the chaincode + +Using the peer1 admin, approve and commit the chaincode (only a single approver is required based on the lifecycle endorsement policy of any organization): + +``` +peer lifecycle chaincode approveformyorg -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --package-id $CHAINCODE_ID --sequence 1 --tls --cafile ${PWD}/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt peer lifecycle chaincode commit -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --sequence 1 --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt ``` +## Interact with the chaincode + Invoke the chaincode to create an asset (only a single endorser is required based on the default endorsement policy of any organization). -Then query the asset, update it, and query again to see the resulting asset changes on the ledger. +Then query the asset, update it, and query again to see the resulting asset changes on the ledger. Note that you need to wait a bit for invoke transactions to complete. ``` peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt diff --git a/test-network-nano-bash/chaincode-external/connection.json b/test-network-nano-bash/chaincode-external/connection.json new file mode 100644 index 00000000..f8b32d79 --- /dev/null +++ b/test-network-nano-bash/chaincode-external/connection.json @@ -0,0 +1,5 @@ +{ + "address": "127.0.0.1:9999", + "dial_timeout": "10s", + "tls_required": false +} diff --git a/test-network-nano-bash/chaincode-external/metadata.json b/test-network-nano-bash/chaincode-external/metadata.json new file mode 100644 index 00000000..a4c90eea --- /dev/null +++ b/test-network-nano-bash/chaincode-external/metadata.json @@ -0,0 +1,4 @@ +{ + "type": "ccaas", + "label": "basic_1.0" +} diff --git a/test-network-nano-bash/generate_artifacts.sh b/test-network-nano-bash/generate_artifacts.sh index bf832367..4a605961 100755 --- a/test-network-nano-bash/generate_artifacts.sh +++ b/test-network-nano-bash/generate_artifacts.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu # remove existing artifacts, or proceed on if the directories don't exist rm -r "${PWD}"/channel-artifacts || true diff --git a/test-network-nano-bash/orderer1.sh b/test-network-nano-bash/orderer1.sh index c06612e0..9ff8b258 100755 --- a/test-network-nano-bash/orderer1.sh +++ b/test-network-nano-bash/orderer1.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/orderer2.sh b/test-network-nano-bash/orderer2.sh index 65a135e0..edb1a432 100755 --- a/test-network-nano-bash/orderer2.sh +++ b/test-network-nano-bash/orderer2.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/orderer3.sh b/test-network-nano-bash/orderer3.sh index 6f2bb4c6..9c5c1481 100755 --- a/test-network-nano-bash/orderer3.sh +++ b/test-network-nano-bash/orderer3.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/peer1.sh b/test-network-nano-bash/peer1.sh index 89d5d8da..bf5764d4 100755 --- a/test-network-nano-bash/peer1.sh +++ b/test-network-nano-bash/peer1.sh @@ -1,7 +1,7 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu -if [ "$(uname)" == "Linux" ] ; then +if [ "$(uname)" = "Linux" ] ; then CCADDR="127.0.0.1" else CCADDR="host.docker.internal" diff --git a/test-network-nano-bash/peer1admin.sh b/test-network-nano-bash/peer1admin.sh index 17e4d619..448a37ad 100755 --- a/test-network-nano-bash/peer1admin.sh +++ b/test-network-nano-bash/peer1admin.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/peer2.sh b/test-network-nano-bash/peer2.sh index 163df06e..8da1bac9 100755 --- a/test-network-nano-bash/peer2.sh +++ b/test-network-nano-bash/peer2.sh @@ -1,7 +1,7 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu -if [ "$(uname)" == "Linux" ] ; then +if [ "$(uname)" = "Linux" ] ; then CCADDR="127.0.0.1" else CCADDR="host.docker.internal" diff --git a/test-network-nano-bash/peer2admin.sh b/test-network-nano-bash/peer2admin.sh index efd5a0c1..6b8e7b1a 100755 --- a/test-network-nano-bash/peer2admin.sh +++ b/test-network-nano-bash/peer2admin.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/peer3.sh b/test-network-nano-bash/peer3.sh index 1054118f..5c976b7f 100755 --- a/test-network-nano-bash/peer3.sh +++ b/test-network-nano-bash/peer3.sh @@ -1,7 +1,7 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu -if [ "$(uname)" == "Linux" ] ; then +if [ "$(uname)" = "Linux" ] ; then CCADDR="127.0.0.1" else CCADDR="host.docker.internal" diff --git a/test-network-nano-bash/peer3admin.sh b/test-network-nano-bash/peer3admin.sh index c9625163..3a187573 100755 --- a/test-network-nano-bash/peer3admin.sh +++ b/test-network-nano-bash/peer3admin.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network-nano-bash/peer4.sh b/test-network-nano-bash/peer4.sh index 22e95ace..b3b0aa14 100755 --- a/test-network-nano-bash/peer4.sh +++ b/test-network-nano-bash/peer4.sh @@ -1,7 +1,7 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu -if [ "$(uname)" == "Linux" ] ; then +if [ "$(uname)" = "Linux" ] ; then CCADDR="127.0.0.1" else CCADDR="host.docker.internal" diff --git a/test-network-nano-bash/peer4admin.sh b/test-network-nano-bash/peer4admin.sh index 07a2373d..967d790e 100755 --- a/test-network-nano-bash/peer4admin.sh +++ b/test-network-nano-bash/peer4admin.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # look for binaries in local dev environment /build/bin directory and then in local samples /bin directory export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" diff --git a/test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md b/test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md index a0788b18..e74e7b0d 100644 --- a/test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md +++ b/test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md @@ -1,6 +1,6 @@ # Running Chaincode as Service with the Test Network -The chaincode-as-a-service feature is a very useful and practical way to run 'Smart Contracts'. Traditionally the Fabric Peer has taken on the role of orchestrating the complete lifecycle of the chaincode. It required access to the Docker Daemon to create images, and start containers. Java, NodeJS and Go chaincode frameworks were explicitly known to the peer including how they should be built and started. +The chaincode-as-a-service feature is a very useful and practical way to run 'Smart Contracts'. Traditionally the Fabric Peer has taken on the role of orchestrating the complete lifecycle of the chaincode. It required access to the Docker Daemon to create images, and start containers. Java, Node.js and Go chaincode frameworks were explicitly known to the peer including how they should be built and started. As a result this makes it very hard to deploy into Kubernetes (K8S) style environments, or to run in any form of debug mode. Additionally, the code is being rebuilt by the peer therefore there is some degree of uncertainty about what dependencies have been pulled in. @@ -12,22 +12,21 @@ We need to use the latest 2.4.1 release as this contains some improvements to ma - The docker image for the peer contains a builder for chaincode-as-a-service preconfigured. This is named 'ccaasbuilder'. This removes the need to build your own external builder and repackage and configure the peer - The `ccaasbuilder` applications are included in the binary tgz archive download for use in other circumstances. The `sampleconfig/core.yaml` is updated as well to refer to 'ccaasbuilder' -- The 2.4.1 Java Chaincode release has been updated to remove the need to write a custom bootstrap main class, similar to the NodeJS Chaincode. It is intended that this will be added to the go chaincode as well. +- The 2.4.1 Java Chaincode release has been updated to remove the need to write a custom bootstrap main class, similar to the Node.js Chaincode. It is intended that this will be added to the go chaincode as well. ## End-to-end with the the test-network -The `test-network` and some of the chaincodes have been updated to support running chaincode-as-a-service. The commands below assume that you've got the latest fabric-samples cloned, along with the latest Fabric docker images. +The `test-network` and some of the chaincodes have been updated to support running chaincode-as-a-service. The commands below assume that you've got the latest fabric-samples cloned, along with the latest Fabric docker images. It's useful to have two terminal windows open, one for starting the Fabric Network, and a second for monitoring all the docker containers. -In your 'monitoring' window, run this to watch all activity from the all the docker containers on the `fabric_test` network; this will monitor all the docker containers that are added to the `fabric-test` network. The network is usually created by the `./network.sh up` command, so remember to delay running this until at lest the network is created. It is possible to precreate the network with `docker network create fabric-test` if you wish. +In your 'monitoring' window, run this to watch all activity from the all the docker containers on the `fabric_test` network; this will monitor all the docker containers that are added to the `fabric-test` network. The network is usually created by the `./network.sh up` command, so remember to delay running this until at least the network is created. It is possible to precreate the network with `docker network create fabric-test` if you wish. ```bash # from the fabric-samples repo ./test-network/monitordocker.sh ``` - In the 'Fabric Network' window, start the test network ```bash @@ -44,13 +43,14 @@ You can run other variants of this command, eg to use CouchDB or CAs, without af Note that the order listed isn't mandatory. The key thing is that the containers are running before the first transaction is set by the peer. Remember that this could be on the `commit` if the `initRequired` flag is set. This sequence can be run as follows + ```bash -./network.sh deployCCAAS -ccn basicts -ccp ../asset-transfer-basic/chaincode-typescript +./network.sh deployCCAAS -ccn basicts -ccp ../asset-transfer-basic/chaincode-typescript ``` This is very similar to the `deployCC` command, it needs the name, and path. But also needs to have the port the chaincode container is going use. As each container is on the `fabric-test` network, you might wish to alter this so there are no collisions with other chaincode containers. -You should be able to see the contract starting in the monitoring window. There will be two containers running, one for org1 and one for org2. The container names contain the organzation/peer and the name of the chaincode. +You should be able to see the contract starting in the monitoring window. There will be two containers running, one for org1 and one for org2. The container names contain the organization/peer and the name of the chaincode. To test things are working you can invoke the 'Contract Metadata' function. For information on how to work as different organizations see [Interacting with the network](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html#interacting-with-the-network) @@ -59,32 +59,33 @@ To test things are working you can invoke the 'Contract Metadata' function. For export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" -export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 +export PATH=${PWD}/../bin:$PATH +export FABRIC_CFG_PATH=${PWD}/../config # invoke the function peer chaincode query -C mychannel -n basicts -c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' | jq ``` -If you don't have `jq` installed omit ` | jq`. The metadata shows the details of the deployed contract and is JSON, so jq makes it easier to read. You can repeat the above commands for org2 to confirm that is working. +If you don't have `jq` installed omit `| jq`. The metadata shows the details of the deployed contract and is JSON, so jq makes it easier to read. You can repeat the above commands for org2 to confirm that is working. -To run the Java example, change the `deployCCAAS` command as follows, This will create a two new containers. +To run the Java example, change the `deployCCAAS` command as follows, This will create two new containers. ```bash -./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-typescript +./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-java ``` - ### Troubleshooting -If the JSON structure passed in is badly formatted JSON this error will be in the peer log +If the JSON structure passed in is badly formatted JSON this error will be in the peer log: ``` ::Error: Failed to unmarshal json: cannot unmarshal string into Go value of type map[string]interface {} command=build ``` -## How to configure each langauge +## How to configure each language Each language can work in the '-as-a-service' mode. Note that the approaches here are based on the very latest libraries. When starting the image you can also specify any of the TLS options or additional logging options for the respective chaincode libraries. @@ -95,7 +96,7 @@ With the v2.4.1 Java Chaincode libraries, there are no code changes to make or b A sample docker run command could be as follows. The two key variables that are needed are the `CHAINCODE_SERVER_ADDRESS` and `CORE_CHAICODE_ID_NAME` -``` +```bash docker run --rm -d --name peer0org1_assettx_ccaas \ --network fabric_test \ -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 \ @@ -103,23 +104,20 @@ A sample docker run command could be as follows. The two key variables that are assettx_ccaas_image:latest ``` -### Nodejs +### Node.js -For NodeJS (JavaScript or TypeScript) chaincode, typically the `package.json` has `fabric-chaincode-node start` as the main start command. To run in the '-as-a-service' mode change this to `fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID` - -### Golang - -TBC +For Node.js (JavaScript or TypeScript) chaincode, typically the `package.json` has `fabric-chaincode-node start` as the main start command. To run in the '-as-a-service' mode change this to `fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID` ## Debugging the Chaincode Running in the '-as-a-service' mode offers options, similar to how the Fabric 'dev' mode works on debugging code. The restrictions of the 'dev' mode don't apply. -There is an option `-ccaasdr false` that can be provided on the `deployCCAAS` command. This will _not_ build the docker image or start a docker container. It does output the commands it would have run. +There is an option `-ccaasdocker false` that can be provided on the `deployCCAAS` command. This will _not_ build the docker image or start a docker container. It does output the commands it would have run. Run this command, and you'll see similar output -``` -./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-java -ccaasdr false + +```bash +./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-java -ccaasdocker false #.... Not building docker image; this the command we would have run docker build -f ../asset-transfer-basic/chaincode-java/Dockerfile -t basicj_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 ../asset-transfer-basic/chaincode-java @@ -128,14 +126,15 @@ Not starting docker containers; these are the commands we would have run docker run --rm -d --name peer0org1_basicj_ccaas --network fabric_test -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basicj_1.0:59dcd73a14e2db8eab7f7683343ce27ac242b93b4e8075605a460d63a0438405 -e CORE_CHAINCODE_ID_NAME=basicj_1.0:59dcd73a14e2db8eab7f7683343ce27ac242b93b4e8075605a460d63a0438405 basicj_ccaas_image:latest ``` -Depending on your directory, and what you need to debug you might need to adjust these commands. +Depending on your directory, and what you need to debug you might need to adjust these commands. ### Building the docker image + The first thing needed is to build the docker image. Remember that so long as the peer can connect to the hostname:port given in the `connection.json` the actual packaging of the chaincode is not important to the peer. You are at liberty to adjust the dockerfiles given hgere. To manually build the docker image for the `asset-transfer-basic/chaincode-java` -``` +```bash docker build -f ../asset-transfer-basic/chaincode-java/Dockerfile -t basicj_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 ../asset-transfer-basic/chaincode-java ``` @@ -144,13 +143,14 @@ docker build -f ../asset-transfer-basic/chaincode-java/Dockerfile -t basicj_ccaa You need to start the docker container. NodeJs for example, could be started like this -``` + +```bash docker run --rm -it -p 9229:9229 --name peer0org2_basic_ccaas --network fabric_test -e DEBUG=true -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basic_1.0:7c7dff5cdc43c77ccea028c422b3348c3c1fb5a26ace0077cf3cc627bd355ef0 -e CORE_CHAINCODE_ID_NAME=basic_1.0:7c7dff5cdc43c77ccea028c422b3348c3c1fb5a26ace0077cf3cc627bd355ef0 basic_ccaas_image:latest ``` Java for example, could be started like this -``` +```bash docker run --rm -it --name peer0org1_basicj_ccaas -p 8000:8000 --network fabric_test -e DEBUG=true -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basicj_1.0:b014a03d8eb1898535e25b4dfeeb3f8244c9f07d91a06aec03e2d19174c45e4f -e CORE_CHAINCODE_ID_NAME=basicj_1.0:b014a03d8e b1898535e25b4dfeeb3f8244c9f07d91a06aec03e2d19174c45e4f basicj_ccaas_image:latest ``` @@ -158,22 +158,22 @@ b1898535e25b4dfeeb3f8244c9f07d91a06aec03e2d19174c45e4f basicj_ccaas_image:lates For all languages please note: - the name of the container needs to match what the peer has in the `connection.json` -- the peer is connecting to the chaincode container via the docker network. Therefore port 9999 does not need to forwarded to the host -- If you are going to single step in a debugger, then you are likely to hit the Fabric transaction timeout value. By default this is 30seconds, meaning the chaincode has to complete transactions in 30 seconds or less. In the `test-network/docker/docker-composer-test-net.yml` add `CORE_CHAINCODE_EXECUTETIMEOUT=300s` to the environment options of each peer. -- In the command above, the `-d` option has been removed from the command the test-network would have used, and has been replaced with `-it`. This means that docker container will not run in detached mode, and will run in the foregroud. +- the peer is connecting to the chaincode container via the docker network. Therefore port 9999 does not need to be forwarded to the host +- If you are going to single step in a debugger, then you are likely to hit the Fabric transaction timeout value. By default this is 30 seconds, meaning the chaincode has to complete transactions in 30 seconds or less. In the `test-network/docker/docker-composer-test-net.yml` add `CORE_CHAINCODE_EXECUTETIMEOUT=300s` to the environment options of each peer. +- In the command above, the `-d` option has been removed from the command the test-network would have used, and has been replaced with `-it`. This means that docker container will not run in detached mode, and will run in the foreground. For Node.js please note: -- Port 9229 is forwarded however - this is the debug port used by NodeJS +- Port 9229 is forwarded however - this is the debug port used by Node.js - `-e DEBUG=true` will trigger the node runtime to be started in debug mode. This is encoded in the `docker/docker-entrypoint.sh` script - this is an example and you may wish to remove this in production images for security - If you are using typescript, ensure that the typescript has been compiled with sourcemaps, otherwise a debugger will struggle matching up the source code. For Java please note: + - Port 800 is forwarded, the debug port for the JVM - `-e DEBUG=true` will trigger the node runtime to be started in debug mode. This is encoded in the `docker/docker-entrypoint.sh` script - this is an example and you may wish to remove this in production images for security - In the java command with the option to start the debugger is `java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar` Note the `0.0.0.0` as the debug port needs to be bound to all network adapters so the debugger can be attached from outside the container - ## Running with multiple peers In the traditional approach, each peer that the chaincode is approved on will have a container running the chaincode. With the '-as-a-service' approach we need to achieve the same architecture. @@ -193,12 +193,12 @@ We can define the address to be a template in the `connection.json` In the peer's environment configuration we then set for org1's peer1 -``` +```bash CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG="{\"peername\":\"org1peer1\"}" ``` The external builder will then resolve this address to be `org1peer1_assettransfer_ccaas:9999` for the peer to use. -Each peer can have there own separate configuration, and therefore different addresses. The JSON string that is set can have any structure, so long as the templates (in golang template syntax) match. +Each peer can have their own separate configuration, and therefore different addresses. The JSON string that is set can have any structure, so long as the templates (in golang template syntax) match. -Any value in the `connection.json` can be templated - but only the values and not the keys. \ No newline at end of file +Any value in the `connection.json` can be templated - but only the values and not the keys. diff --git a/test-network/README.md b/test-network/README.md index d982d51e..014d3831 100644 --- a/test-network/README.md +++ b/test-network/README.md @@ -1,13 +1,14 @@ -## Running the test network +# Running the test network You can use the `./network.sh` script to stand up a simple Fabric test network. The test network has two peer organizations with one peer each and a single node raft ordering service. You can also use the `./network.sh` script to create channels and deploy chaincode. For more information, see [Using the Fabric test network](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html). The test network is being introduced in Fabric v2.0 as the long term replacement for the `first-network` sample. Before you can deploy the test network, you need to follow the instructions to [Install the Samples, Binaries and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) in the Hyperledger Fabric documentation. ## Using the Peer commands -The `setOrgEnv.sh` script can be used to setup the environment variables for the ogrganziations, this will will help to be able to use the `peer` commands directly. -First, ensure that the peer binaries are on your path, and the Fabric Config path is set Assuming that you're in the `test-network` directory. +The `setOrgEnv.sh` script can be used to set up the environment variables for the organizations, this will help to be able to use the `peer` commands directly. + +First, ensure that the peer binaries are on your path, and the Fabric Config path is set assuming that you're in the `test-network` directory. ```bash export PATH=$PATH:$(realpath ../bin) @@ -20,11 +21,30 @@ You can then set up the environment variables for each organization. The `./setO export $(./setOrgEnv.sh Org2 | xargs) ``` -(Note bash v4 is required for the scripts) +(Note bash v4 is required for the scripts.) -You will now be able to run the `peer` commands in the context of Org2. If a different command prompt you can run the same command with Org1 instead. -The `setOrgEnv` script outputs a series of `=` strings. These can then be fed into the export command for your current shell +You will now be able to run the `peer` commands in the context of Org2. If a different command prompt, you can run the same command with Org1 instead. +The `setOrgEnv` script outputs a series of `=` strings. These can then be fed into the export command for your current shell. ## Chaincode-as-a-service -To learn more about how to use the improvements to the Chaincode-as-a-service please see this [tutorial](./test-network/../CHAINCODE_AS_A_SERVICE_TUTORIAL.md). It is expected that this will move to augment the tutorial in the [Hyperledger Fabric ReadTheDocs](https://hyperledger-fabric.readthedocs.io/en/release-2.4/cc_service.html) \ No newline at end of file +To learn more about how to use the improvements to the Chaincode-as-a-service please see this [tutorial](./test-network/../CHAINCODE_AS_A_SERVICE_TUTORIAL.md). It is expected that this will move to augment the tutorial in the [Hyperledger Fabric ReadTheDocs](https://hyperledger-fabric.readthedocs.io/en/release-2.4/cc_service.html) + + +## Podman + +*Note - podman support should be considered experimental. There are issues with volume mounting on MacOS that prevent this working. If wish to use podman a LinuxVM is suggested.* + +A copy of the `install_fabric.sh` script is in the `test-network` directory. This has been enhanced to support a `podman` argument; if used it will use the `podman` command to pull down images and tag them rather than docker. The images are the same, just pulled differently + +The `network.sh` script has been enhanced so that it can use `podman` and `podman-compose` instead of docker. Ensure that `CONTAINER_CLI` is set as below when running `network.sh` script. + +```bash +CONTAINER_CLI=podman ./network.sh up +```` + +As there is no Docker-Daemon when using podman, only the `./network.sh deployCCAAS` command will work. + + + + diff --git a/test-network/addOrg3/addOrg3.sh b/test-network/addOrg3/addOrg3.sh index 0700095a..d21e6d76 100755 --- a/test-network/addOrg3/addOrg3.sh +++ b/test-network/addOrg3/addOrg3.sh @@ -17,6 +17,11 @@ export VERBOSE=false . ../scripts/utils.sh +: ${CONTAINER_CLI:="docker"} +: ${CONTAINER_CLI_COMPOSE:="${CONTAINER_CLI}-compose"} +infoln "Using ${CONTAINER_CLI} and ${CONTAINER_CLI_COMPOSE}" + + # Print the usage message function printHelp () { echo "Usage: " @@ -84,7 +89,7 @@ function generateOrg3() { fi infoln "Generating certificates using Fabric CA" - docker-compose -f $COMPOSE_FILE_CA_ORG3 up -d 2>&1 + ${CONTAINER_CLI_COMPOSE} -f ${COMPOSE_FILE_CA_BASE} -f $COMPOSE_FILE_CA_ORG3 up -d 2>&1 . fabric-ca/registerEnroll.sh @@ -118,10 +123,15 @@ function generateOrg3Definition() { function Org3Up () { # start org3 nodes + + if [ "$CONTAINER_CLI" == "podman" ]; then + cp ../podman/core.yaml ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/ + fi + if [ "${DATABASE}" == "couchdb" ]; then - DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1 + DOCKER_SOCK=${DOCKER_SOCK} ${CONTAINER_CLI_COMPOSE} -f ${COMPOSE_FILE_BASE} -f $COMPOSE_FILE_ORG3 -f ${COMPOSE_FILE_COUCH_BASE} -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1 else - DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1 + DOCKER_SOCK=${DOCKER_SOCK} ${CONTAINER_CLI_COMPOSE} -f ${COMPOSE_FILE_BASE} -f $COMPOSE_FILE_ORG3 up -d 2>&1 fi if [ $? -ne 0 ]; then fatalln "ERROR !!!! Unable to start Org3 network" @@ -147,13 +157,13 @@ function addOrg3 () { # Use the CLI container to create the configuration transaction needed to add # Org3 to the network infoln "Generating and submitting config tx to add Org3" - docker exec cli ./scripts/org3-scripts/updateChannelConfig.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE + ${CONTAINER_CLI} exec cli ./scripts/org3-scripts/updateChannelConfig.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE if [ $? -ne 0 ]; then fatalln "ERROR !!!! Unable to create config tx" fi infoln "Joining Org3 peers to network" - docker exec cli ./scripts/org3-scripts/joinChannel.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE + ${CONTAINER_CLI} exec cli ./scripts/org3-scripts/joinChannel.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE if [ $? -ne 0 ]; then fatalln "ERROR !!!! Unable to join Org3 peers to network" fi @@ -175,11 +185,14 @@ CLI_DELAY=3 # channel name defaults to "mychannel" CHANNEL_NAME="mychannel" # use this as the docker compose couch file -COMPOSE_FILE_COUCH_ORG3=docker/docker-compose-couch-org3.yaml +COMPOSE_FILE_COUCH_BASE=compose/compose-couch-org3.yaml +COMPOSE_FILE_COUCH_ORG3=compose/${CONTAINER_CLI}/docker-compose-couch-org3.yaml # use this as the default docker-compose yaml definition -COMPOSE_FILE_ORG3=docker/docker-compose-org3.yaml +COMPOSE_FILE_BASE=compose/compose-org3.yaml +COMPOSE_FILE_ORG3=compose/${CONTAINER_CLI}/docker-compose-org3.yaml # certificate authorities compose file -COMPOSE_FILE_CA_ORG3=docker/docker-compose-ca-org3.yaml +COMPOSE_FILE_CA_BASE=compose/compose-ca-org3.yaml +COMPOSE_FILE_CA_ORG3=compose/${CONTAINER_CLI}/docker-compose-ca-org3.yaml # database DATABASE="leveldb" diff --git a/test-network/addOrg3/docker/docker-compose-ca-org3.yaml b/test-network/addOrg3/compose/compose-ca-org3.yaml similarity index 100% rename from test-network/addOrg3/docker/docker-compose-ca-org3.yaml rename to test-network/addOrg3/compose/compose-ca-org3.yaml diff --git a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml b/test-network/addOrg3/compose/compose-couch-org3.yaml similarity index 100% rename from test-network/addOrg3/docker/docker-compose-couch-org3.yaml rename to test-network/addOrg3/compose/compose-couch-org3.yaml diff --git a/test-network/addOrg3/docker/docker-compose-org3.yaml b/test-network/addOrg3/compose/compose-org3.yaml similarity index 80% rename from test-network/addOrg3/docker/docker-compose-org3.yaml rename to test-network/addOrg3/compose/compose-org3.yaml index a596ea56..f6e1ca1b 100644 --- a/test-network/addOrg3/docker/docker-compose-org3.yaml +++ b/test-network/addOrg3/compose/compose-org3.yaml @@ -20,9 +20,8 @@ services: labels: service: hyperledger-fabric environment: + - FABRIC_CFG_PATH=/etc/hyperledger/peercfg #Generic peer variables - - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test - FABRIC_LOGGING_SPEC=INFO #- FABRIC_LOGGING_SPEC=DEBUG - CORE_PEER_TLS_ENABLED=true @@ -33,16 +32,18 @@ services: # Peer specific variables - CORE_PEER_ID=peer0.org3.example.com - CORE_PEER_ADDRESS=peer0.org3.example.com:11051 + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp - CORE_PEER_LISTENADDRESS=0.0.0.0:11051 - CORE_PEER_CHAINCODEADDRESS=peer0.org3.example.com:11052 - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:11052 - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org3.example.com:11051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051 - CORE_PEER_LOCALMSPID=Org3MSP + - CORE_METRICS_PROVIDER=prometheus + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG={"peername":"peer0org1"} + - CORE_CHAINCODE_EXECUTETIMEOUT=300s volumes: - - ${DOCKER_SOCK}:/host/var/run/docker.sock - - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp - - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls + - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com:/etc/hyperledger/fabric - peer0.org3.example.com:/var/hyperledger/production working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: peer node start diff --git a/test-network/addOrg3/compose/docker/docker-compose-ca-org3.yaml b/test-network/addOrg3/compose/docker/docker-compose-ca-org3.yaml new file mode 100644 index 00000000..16732f0c --- /dev/null +++ b/test-network/addOrg3/compose/docker/docker-compose-ca-org3.yaml @@ -0,0 +1,7 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + diff --git a/test-network/addOrg3/compose/docker/docker-compose-couch-org3.yaml b/test-network/addOrg3/compose/docker/docker-compose-couch-org3.yaml new file mode 100644 index 00000000..16732f0c --- /dev/null +++ b/test-network/addOrg3/compose/docker/docker-compose-couch-org3.yaml @@ -0,0 +1,7 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + diff --git a/test-network/addOrg3/compose/docker/docker-compose-org3.yaml b/test-network/addOrg3/compose/docker/docker-compose-org3.yaml new file mode 100644 index 00000000..12c9ea71 --- /dev/null +++ b/test-network/addOrg3/compose/docker/docker-compose-org3.yaml @@ -0,0 +1,25 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + +networks: + test: + name: fabric_test + +services: + + peer0.org3.example.com: + container_name: peer0.org3.example.com + image: hyperledger/fabric-peer:latest + labels: + service: hyperledger-fabric + environment: + #Generic peer variables + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test + volumes: + - ./docker/peercfg:/etc/hyperledger/peercfg + - ${DOCKER_SOCK}:/host/var/run/docker.sock diff --git a/test-network/addOrg3/compose/docker/peercfg/core.yaml b/test-network/addOrg3/compose/docker/peercfg/core.yaml new file mode 100644 index 00000000..16e5b606 --- /dev/null +++ b/test-network/addOrg3/compose/docker/peercfg/core.yaml @@ -0,0 +1,777 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################### +# +# Peer section +# +############################################################################### +peer: + + # The peer id provides a name for this peer instance and is used when + # naming docker resources. + id: jdoe + + # The networkId allows for logical separation of networks and is used when + # naming docker resources. + networkId: dev + + # The Address at local network interface this Peer will listen on. + # By default, it will listen on all network interfaces + listenAddress: 0.0.0.0:7051 + + # The endpoint this peer uses to listen for inbound chaincode connections. + # If this is commented-out, the listen address is selected to be + # the peer's address (see below) with port 7052 + # chaincodeListenAddress: 0.0.0.0:7052 + + # The endpoint the chaincode for this peer uses to connect to the peer. + # If this is not specified, the chaincodeListenAddress address is selected. + # And if chaincodeListenAddress is not specified, address is selected from + # peer address (see below). If specified peer address is invalid then it + # will fallback to the auto detected IP (local IP) regardless of the peer + # addressAutoDetect value. + # chaincodeAddress: 0.0.0.0:7052 + + # When used as peer config, this represents the endpoint to other peers + # in the same organization. For peers in other organization, see + # gossip.externalEndpoint for more info. + # When used as CLI config, this means the peer's endpoint to interact with + address: 0.0.0.0:7051 + + # Whether the Peer should programmatically determine its address + # This case is useful for docker containers. + # When set to true, will override peer address. + addressAutoDetect: false + + # Settings for the Peer's gateway server. + gateway: + # Whether the gateway is enabled for this Peer. + enabled: true + # endorsementTimeout is the duration the gateway waits for a response + # from other endorsing peers before returning a timeout error to the client. + endorsementTimeout: 30s + # dialTimeout is the duration the gateway waits for a connection + # to other network nodes. + dialTimeout: 2m + + + # Keepalive settings for peer server and clients + keepalive: + # Interval is the duration after which if the server does not see + # any activity from the client it pings the client to see if it's alive + interval: 7200s + # Timeout is the duration the server waits for a response + # from the client after sending a ping before closing the connection + timeout: 20s + # MinInterval is the minimum permitted time between client pings. + # If clients send pings more frequently, the peer server will + # disconnect them + minInterval: 60s + # Client keepalive settings for communicating with other peer nodes + client: + # Interval is the time between pings to peer nodes. This must + # greater than or equal to the minInterval specified by peer + # nodes + interval: 60s + # Timeout is the duration the client waits for a response from + # peer nodes before closing the connection + timeout: 20s + # DeliveryClient keepalive settings for communication with ordering + # nodes. + deliveryClient: + # Interval is the time between pings to ordering nodes. This must + # greater than or equal to the minInterval specified by ordering + # nodes. + interval: 60s + # Timeout is the duration the client waits for a response from + # ordering nodes before closing the connection + timeout: 20s + + + # Gossip related configuration + gossip: + # Bootstrap set to initialize gossip with. + # This is a list of other peers that this peer reaches out to at startup. + # Important: The endpoints here have to be endpoints of peers in the same + # organization, because the peer would refuse connecting to these endpoints + # unless they are in the same organization as the peer. + bootstrap: 127.0.0.1:7051 + + # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. + # Setting both to true would result in the termination of the peer + # since this is undefined state. If the peers are configured with + # useLeaderElection=false, make sure there is at least 1 peer in the + # organization that its orgLeader is set to true. + + # Defines whenever peer will initialize dynamic algorithm for + # "leader" selection, where leader is the peer to establish + # connection with ordering service and use delivery protocol + # to pull ledger blocks from ordering service. + useLeaderElection: false + # Statically defines peer to be an organization "leader", + # where this means that current peer will maintain connection + # with ordering service and disseminate block across peers in + # its own organization. Multiple peers or all peers in an organization + # may be configured as org leaders, so that they all pull + # blocks directly from ordering service. + orgLeader: true + + # Interval for membershipTracker polling + membershipTrackerInterval: 5s + + # Overrides the endpoint that the peer publishes to peers + # in its organization. For peers in foreign organizations + # see 'externalEndpoint' + endpoint: + # Maximum count of blocks stored in memory + maxBlockCountToStore: 10 + # Max time between consecutive message pushes(unit: millisecond) + maxPropagationBurstLatency: 10ms + # Max number of messages stored until a push is triggered to remote peers + maxPropagationBurstSize: 10 + # Number of times a message is pushed to remote peers + propagateIterations: 1 + # Number of peers selected to push messages to + propagatePeerNum: 3 + # Determines frequency of pull phases(unit: second) + # Must be greater than digestWaitTime + responseWaitTime + pullInterval: 4s + # Number of peers to pull from + pullPeerNum: 3 + # Determines frequency of pulling state info messages from peers(unit: second) + requestStateInfoInterval: 4s + # Determines frequency of pushing state info messages to peers(unit: second) + publishStateInfoInterval: 4s + # Maximum time a stateInfo message is kept until expired + stateInfoRetentionInterval: + # Time from startup certificates are included in Alive messages(unit: second) + publishCertPeriod: 10s + # Should we skip verifying block messages or not (currently not in use) + skipBlockVerification: false + # Dial timeout(unit: second) + dialTimeout: 3s + # Connection timeout(unit: second) + connTimeout: 2s + # Buffer size of received messages + recvBuffSize: 20 + # Buffer size of sending messages + sendBuffSize: 200 + # Time to wait before pull engine processes incoming digests (unit: second) + # Should be slightly smaller than requestWaitTime + digestWaitTime: 1s + # Time to wait before pull engine removes incoming nonce (unit: milliseconds) + # Should be slightly bigger than digestWaitTime + requestWaitTime: 1500ms + # Time to wait before pull engine ends pull (unit: second) + responseWaitTime: 2s + # Alive check interval(unit: second) + aliveTimeInterval: 5s + # Alive expiration timeout(unit: second) + aliveExpirationTimeout: 25s + # Reconnect interval(unit: second) + reconnectInterval: 25s + # Max number of attempts to connect to a peer + maxConnectionAttempts: 120 + # Message expiration factor for alive messages + msgExpirationFactor: 20 + # This is an endpoint that is published to peers outside of the organization. + # If this isn't set, the peer will not be known to other organizations. + externalEndpoint: + # Leader election service configuration + election: + # Longest time peer waits for stable membership during leader election startup (unit: second) + startupGracePeriod: 15s + # Interval gossip membership samples to check its stability (unit: second) + membershipSampleInterval: 1s + # Time passes since last declaration message before peer decides to perform leader election (unit: second) + leaderAliveThreshold: 10s + # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) + leaderElectionDuration: 5s + + pvtData: + # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block + # would be attempted to be pulled from peers until the block would be committed without the private data + pullRetryThreshold: 60s + # As private data enters the transient store, it is associated with the peer's ledger's height at that time. + # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, + # and the private data residing inside the transient store that is guaranteed not to be purged. + # Private data is purged from the transient store when blocks with sequences that are multiples + # of transientstoreMaxBlockRetention are committed. + transientstoreMaxBlockRetention: 1000 + # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer + # at private data push at endorsement time. + pushAckTimeout: 3s + # Block to live pulling margin, used as a buffer + # to prevent peer from trying to pull private data + # from peers that is soon to be purged in next N blocks. + # This helps a newly joined peer catch up to current + # blockchain height quicker. + btlPullMargin: 10 + # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to + # pull from the other peers the most recent missing blocks with a maximum batch size limitation. + # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a + # single iteration. + reconcileBatchSize: 10 + # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning + # of the next reconciliation iteration. + reconcileSleepInterval: 1m + # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. + reconciliationEnabled: true + # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid + # transaction's private data from other peers need to be skipped during the commit time and pulled + # only through reconciler. + skipPullingInvalidTransactionsDuringCommit: false + # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. + # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values + # for disseminating private data. + # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to + # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. + implicitCollectionDisseminationPolicy: + # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully + # disseminate private data for its own implicit collection during endorsement. Default value is 0. + requiredPeerCount: 0 + # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to + # disseminate private data for its own implicit collection during endorsement. Default value is 1. + maxPeerCount: 1 + + # Gossip state transfer related configuration + state: + # indicates whenever state transfer is enabled or not + # default value is false, i.e. state transfer is active + # and takes care to sync up missing blocks allowing + # lagging peer to catch up to speed with rest network. + # Keep in mind that when peer.gossip.useLeaderElection is true + # and there are several peers in the organization, + # or peer.gossip.useLeaderElection is false alongside with + # peer.gossip.orgleader being false, the peer's ledger may lag behind + # the rest of the peers and will never catch up due to state transfer + # being disabled. + enabled: false + # checkInterval interval to check whether peer is lagging behind enough to + # request blocks via state transfer from another peer. + checkInterval: 10s + # responseTimeout amount of time to wait for state transfer response from + # other peers + responseTimeout: 3s + # batchSize the number of blocks to request via state transfer from another peer + batchSize: 10 + # blockBufferSize reflects the size of the re-ordering buffer + # which captures blocks and takes care to deliver them in order + # down to the ledger layer. The actual buffer size is bounded between + # 0 and 2*blockBufferSize, each channel maintains its own buffer + blockBufferSize: 20 + # maxRetries maximum number of re-tries to ask + # for single state transfer request + maxRetries: 3 + + # TLS Settings + tls: + # Require server-side TLS + enabled: false + # Require client certificates / mutual TLS for inbound connections. + # Note that clients that are not configured to use a certificate will + # fail to connect to the peer. + clientAuthRequired: false + # X.509 certificate used for TLS server + cert: + file: tls/server.crt + # Private key used for TLS server + key: + file: tls/server.key + # rootcert.file represents the trusted root certificate chain used for verifying certificates + # of other nodes during outbound connections. + # It is not required to be set, but can be used to augment the set of TLS CA certificates + # available from the MSPs of each channel’s configuration. + rootcert: + file: tls/ca.crt + # If mutual TLS is enabled, clientRootCAs.files contains a list of additional root certificates + # used for verifying certificates of client connections. + # It augments the set of TLS CA certificates available from the MSPs of each channel’s configuration. + # Minimally, set your organization's TLS CA root certificate so that the peer can receive join channel requests. + clientRootCAs: + files: + - tls/ca.crt + # Private key used for TLS when making client connections. + # If not set, peer.tls.key.file will be used instead + clientKey: + file: + # X.509 certificate used for TLS when making client connections. + # If not set, peer.tls.cert.file will be used instead + clientCert: + file: + + # Authentication contains configuration parameters related to authenticating + # client messages + authentication: + # the acceptable difference between the current server time and the + # client's time as specified in a client request message + timewindow: 15m + + # Path on the file system where peer will store data (eg ledger). This + # location must be access control protected to prevent unintended + # modification that might corrupt the peer operations. + fileSystemPath: /var/hyperledger/production + + # BCCSP (Blockchain crypto provider): Select which crypto implementation or + # library to use + BCCSP: + Default: SW + # Settings for the SW crypto provider (i.e. when DEFAULT: SW) + SW: + # TODO: The default Hash and Security level needs refactoring to be + # fully configurable. Changing these defaults requires coordination + # SHA2 is hardcoded in several places, not only BCCSP + Hash: SHA2 + Security: 256 + # Location of Key Store + FileKeyStore: + # If "", defaults to 'mspConfigPath'/keystore + KeyStore: + # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) + PKCS11: + # Location of the PKCS11 module library + Library: + # Token Label + Label: + # User PIN + Pin: + Hash: + Security: + + # Path on the file system where peer will find MSP local configurations + mspConfigPath: mspreally + # Identifier of the local MSP + # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- + # Deployers need to change the value of the localMspId string. + # In particular, the name of the local MSP ID of a peer needs + # to match the name of one of the MSPs in each of the channel + # that this peer is a member of. Otherwise this peer's messages + # will not be identified as valid by other nodes. + localMspId: SampleOrg + + # CLI common client config options + client: + # connection timeout + connTimeout: 3s + + # Delivery service related config + deliveryclient: + # Enables this peer to disseminate blocks it pulled from the ordering service + # via gossip. + # Note that 'gossip.state.enabled' controls point to point block replication + # of blocks committed in the past. + blockGossipEnabled: true + # It sets the total time the delivery service may spend in reconnection + # attempts until its retry logic gives up and returns an error + reconnectTotalTimeThreshold: 3600s + + # It sets the delivery service <-> ordering service node connection timeout + connTimeout: 3s + + # It sets the delivery service maximal delay between consecutive retries + reConnectBackoffThreshold: 3600s + + # A list of orderer endpoint addresses which should be overridden + # when found in channel configurations. + addressOverrides: + # - from: + # to: + # caCertsFile: + # - from: + # to: + # caCertsFile: + + # Type for the local MSP - by default it's of type bccsp + localMspType: bccsp + + # Used with Go profiling tools only in none production environment. In + # production, it should be disabled (eg enabled: false) + profile: + enabled: false + listenAddress: 0.0.0.0:6060 + + # Handlers defines custom handlers that can filter and mutate + # objects passing within the peer, such as: + # Auth filter - reject or forward proposals from clients + # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation + # Valid handler definition contains: + # - A name which is a factory method name defined in + # core/handlers/library/library.go for statically compiled handlers + # - library path to shared object binary for pluggable filters + # Auth filters and decorators are chained and executed in the order that + # they are defined. For example: + # authFilters: + # - + # name: FilterOne + # library: /opt/lib/filter.so + # - + # name: FilterTwo + # decorators: + # - + # name: DecoratorOne + # - + # name: DecoratorTwo + # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so + handlers: + authFilters: + - + name: DefaultAuth + - + name: ExpirationCheck # This filter checks identity x509 certificate expiration + decorators: + - + name: DefaultDecorator + endorsers: + escc: + name: DefaultEndorsement + library: + validators: + vscc: + name: DefaultValidation + library: + + # library: /etc/hyperledger/fabric/plugin/escc.so + # Number of goroutines that will execute transaction validation in parallel. + # By default, the peer chooses the number of CPUs on the machine. Set this + # variable to override that choice. + # NOTE: overriding this value might negatively influence the performance of + # the peer so please change this value only if you know what you're doing + validatorPoolSize: + + # The discovery service is used by clients to query information about peers, + # such as - which peers have joined a certain channel, what is the latest + # channel config, and most importantly - given a chaincode and a channel, + # what possible sets of peers satisfy the endorsement policy. + discovery: + enabled: true + # Whether the authentication cache is enabled or not. + authCacheEnabled: true + # The maximum size of the cache, after which a purge takes place + authCacheMaxSize: 1000 + # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation + authCachePurgeRetentionRatio: 0.75 + # Whether to allow non-admins to perform non channel scoped queries. + # When this is false, it means that only peer admins can perform non channel scoped queries. + orgMembersAllowedAccess: false + + # Limits is used to configure some internal resource limits. + limits: + # Concurrency limits the number of concurrently running requests to a service on each peer. + # Currently this option is only applied to endorser service and deliver service. + # When the property is missing or the value is 0, the concurrency limit is disabled for the service. + concurrency: + # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, + # including both user chaincodes and system chaincodes. + endorserService: 2500 + # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. + deliverService: 2500 + + # Since all nodes should be consistent it is recommended to keep + # the default value of 100MB for MaxRecvMsgSize & MaxSendMsgSize + # Max message size in bytes GRPC server and client can receive + maxRecvMsgSize: 104857600 + # Max message size in bytes GRPC server and client can send + maxSendMsgSize: 104857600 + +############################################################################### +# +# VM section +# +############################################################################### +vm: + + # Endpoint of the vm management system. For docker can be one of the following in general + # unix:///var/run/docker.sock + # http://localhost:2375 + # https://localhost:2376 + # If you utilize external chaincode builders and don't need the default Docker chaincode builder, + # the endpoint should be unconfigured so that the peer's Docker health checker doesn't get registered. + endpoint: unix:///var/run/docker.sock + + # settings for docker vms + docker: + tls: + enabled: false + ca: + file: docker/ca.crt + cert: + file: docker/tls.crt + key: + file: docker/tls.key + + # Enables/disables the standard out/err from chaincode containers for + # debugging purposes + attachStdout: false + + # Parameters on creating docker container. + # Container may be efficiently created using ipam & dns-server for cluster + # NetworkMode - sets the networking mode for the container. Supported + # standard values are: `host`(default),`bridge`,`ipvlan`,`none`. + # Dns - a list of DNS servers for the container to use. + # Note: `Privileged` `Binds` `Links` and `PortBindings` properties of + # Docker Host Config are not supported and will not be used if set. + # LogConfig - sets the logging driver (Type) and related options + # (Config) for Docker. For more info, + # https://docs.docker.com/engine/admin/logging/overview/ + # Note: Set LogConfig using Environment Variables is not supported. + hostConfig: + NetworkMode: host + Dns: + # - 192.168.0.1 + LogConfig: + Type: json-file + Config: + max-size: "50m" + max-file: "5" + Memory: 2147483648 + +############################################################################### +# +# Chaincode section +# +############################################################################### +chaincode: + + # The id is used by the Chaincode stub to register the executing Chaincode + # ID with the Peer and is generally supplied through ENV variables + # the `path` form of ID is provided when installing the chaincode. + # The `name` is used for all other requests and can be any string. + id: + path: + name: + + # Generic builder environment, suitable for most chaincode types + builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) + + # Enables/disables force pulling of the base docker images (listed below) + # during user chaincode instantiation. + # Useful when using moving image tags (such as :latest) + pull: false + + golang: + # golang will never need more than baseos + runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) + + # whether or not golang chaincode should be linked dynamically + dynamicLink: false + + java: + # This is an image based on java:openjdk-8 with addition compiler + # tools added for java shim layer packaging. + # This image is packed with shim layer libraries that are necessary + # for Java chaincode runtime. + runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) + + node: + # This is an image based on node:$(NODE_VER)-alpine + runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) + + # List of directories to treat as external builders and launchers for + # chaincode. The external builder detection processing will iterate over the + # builders in the order specified below. + # If you don't need to fallback to the default Docker builder, also unconfigure vm.endpoint above. + # To override this property via env variable use CORE_CHAINCODE_EXTERNALBUILDERS: [{name: x, path: dir1}, {name: y, path: dir2}] + externalBuilders: + - name: ccaas_builder + path: /opt/hyperledger/ccaas_builder + propagateEnvironment: + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG + + + # The maximum duration to wait for the chaincode build and install process + # to complete. + installTimeout: 300s + + # Timeout duration for starting up a container and waiting for Register + # to come through. + startuptimeout: 300s + + # Timeout duration for Invoke and Init calls to prevent runaway. + # This timeout is used by all chaincodes in all the channels, including + # system chaincodes. + # Note that during Invoke, if the image is not available (e.g. being + # cleaned up when in development environment), the peer will automatically + # build the image, which might take more time. In production environment, + # the chaincode image is unlikely to be deleted, so the timeout could be + # reduced accordingly. + executetimeout: 30s + + # There are 2 modes: "dev" and "net". + # In dev mode, user runs the chaincode after starting peer from + # command line on local machine. + # In net mode, peer will run chaincode in a docker container. + mode: net + + # keepalive in seconds. In situations where the communication goes through a + # proxy that does not support keep-alive, this parameter will maintain connection + # between peer and chaincode. + # A value <= 0 turns keepalive off + keepalive: 0 + + # enabled system chaincodes + system: + _lifecycle: enable + cscc: enable + lscc: enable + qscc: enable + + # Logging section for the chaincode container + logging: + # Default level for all loggers within the chaincode container + level: info + # Override default level for the 'shim' logger + shim: warning + # Format for the chaincode container logs + format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' + +############################################################################### +# +# Ledger section - ledger configuration encompasses both the blockchain +# and the state +# +############################################################################### +ledger: + + blockchain: + + state: + # stateDatabase - options are "goleveldb", "CouchDB" + # goleveldb - default state database stored in goleveldb. + # CouchDB - store state database in CouchDB + stateDatabase: goleveldb + # Limit on the number of records to return per query + totalQueryLimit: 100000 + couchDBConfig: + # It is recommended to run CouchDB on the same server as the peer, and + # not map the CouchDB container port to a server port in docker-compose. + # Otherwise proper security must be provided on the connection between + # CouchDB client (on the peer) and server. + couchDBAddress: 127.0.0.1:5984 + # This username must have read and write authority on CouchDB + username: + # The password is recommended to pass as an environment variable + # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). + # If it is stored here, the file must be access control protected + # to prevent unintended users from discovering the password. + password: + # Number of retries for CouchDB errors + maxRetries: 3 + # Number of retries for CouchDB errors during peer startup. + # The delay between retries doubles for each attempt. + # Default of 10 retries results in 11 attempts over 2 minutes. + maxRetriesOnStartup: 10 + # CouchDB request timeout (unit: duration, e.g. 20s) + requestTimeout: 35s + # Limit on the number of records per each CouchDB query + # Note that chaincode queries are only bound by totalQueryLimit. + # Internally the chaincode may execute multiple CouchDB queries, + # each of size internalQueryLimit. + internalQueryLimit: 1000 + # Limit on the number of records per CouchDB bulk update batch + maxBatchUpdateSize: 1000 + # Create the _global_changes system database + # This is optional. Creating the global changes database will require + # additional system resources to track changes and maintain the database + createGlobalChangesDB: false + # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state + # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple + # of 32 MB, the peer would round the size to the next multiple of 32 MB. + # To disable the cache, 0 MB needs to be assigned to the cacheSize. + cacheSize: 64 + + history: + # enableHistoryDatabase - options are true or false + # Indicates if the history of key updates should be stored. + # All history 'index' will be stored in goleveldb, regardless if using + # CouchDB or alternate database for the state. + enableHistoryDatabase: true + + pvtdataStore: + # the maximum db batch size for converting + # the ineligible missing data entries to eligible missing data entries + collElgProcMaxDbBatchSize: 5000 + # the minimum duration (in milliseconds) between writing + # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries + collElgProcDbBatchesInterval: 1000 + # The missing data entries are classified into two categories: + # (1) prioritized + # (2) deprioritized + # Initially, all missing data are in the prioritized list. When the + # reconciler is unable to fetch the missing data from other peers, + # the unreconciled missing data would be moved to the deprioritized list. + # The reconciler would retry deprioritized missing data after every + # deprioritizedDataReconcilerInterval (unit: minutes). Note that the + # interval needs to be greater than the reconcileSleepInterval + deprioritizedDataReconcilerInterval: 60m + + snapshots: + # Path on the file system where peer will store ledger snapshots + rootDir: /var/hyperledger/production/snapshots + +############################################################################### +# +# Operations section +# +############################################################################### +operations: + # host and port for the operations server + listenAddress: 127.0.0.1:9443 + + # 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: + + # most operations service endpoints require client authentication when TLS + # is enabled. clientAuthRequired requires client certificate authentication + # at the TLS layer to access all resources. + clientAuthRequired: false + + # paths to PEM encoded ca certificates to trust for client authentication + clientRootCAs: + files: [] + +############################################################################### +# +# Metrics section +# +############################################################################### +metrics: + # metrics provider is one of 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 pushed + # to statsd; timings are pushed immediately + writeInterval: 10s + + # prefix is prepended to all emitted statsd metrics + prefix: diff --git a/test-network/addOrg3/compose/podman/peercfg/core.yaml b/test-network/addOrg3/compose/podman/peercfg/core.yaml new file mode 100644 index 00000000..4b173e63 --- /dev/null +++ b/test-network/addOrg3/compose/podman/peercfg/core.yaml @@ -0,0 +1,777 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################### +# +# Peer section +# +############################################################################### +peer: + + # The peer id provides a name for this peer instance and is used when + # naming docker resources. + id: jdoe + + # The networkId allows for logical separation of networks and is used when + # naming docker resources. + networkId: dev + + # The Address at local network interface this Peer will listen on. + # By default, it will listen on all network interfaces + listenAddress: 0.0.0.0:7051 + + # The endpoint this peer uses to listen for inbound chaincode connections. + # If this is commented-out, the listen address is selected to be + # the peer's address (see below) with port 7052 + # chaincodeListenAddress: 0.0.0.0:7052 + + # The endpoint the chaincode for this peer uses to connect to the peer. + # If this is not specified, the chaincodeListenAddress address is selected. + # And if chaincodeListenAddress is not specified, address is selected from + # peer address (see below). If specified peer address is invalid then it + # will fallback to the auto detected IP (local IP) regardless of the peer + # addressAutoDetect value. + # chaincodeAddress: 0.0.0.0:7052 + + # When used as peer config, this represents the endpoint to other peers + # in the same organization. For peers in other organization, see + # gossip.externalEndpoint for more info. + # When used as CLI config, this means the peer's endpoint to interact with + address: 0.0.0.0:7051 + + # Whether the Peer should programmatically determine its address + # This case is useful for docker containers. + # When set to true, will override peer address. + addressAutoDetect: false + + # Settings for the Peer's gateway server. + gateway: + # Whether the gateway is enabled for this Peer. + enabled: true + # endorsementTimeout is the duration the gateway waits for a response + # from other endorsing peers before returning a timeout error to the client. + endorsementTimeout: 30s + # dialTimeout is the duration the gateway waits for a connection + # to other network nodes. + dialTimeout: 2m + + + # Keepalive settings for peer server and clients + keepalive: + # Interval is the duration after which if the server does not see + # any activity from the client it pings the client to see if it's alive + interval: 7200s + # Timeout is the duration the server waits for a response + # from the client after sending a ping before closing the connection + timeout: 20s + # MinInterval is the minimum permitted time between client pings. + # If clients send pings more frequently, the peer server will + # disconnect them + minInterval: 60s + # Client keepalive settings for communicating with other peer nodes + client: + # Interval is the time between pings to peer nodes. This must + # greater than or equal to the minInterval specified by peer + # nodes + interval: 60s + # Timeout is the duration the client waits for a response from + # peer nodes before closing the connection + timeout: 20s + # DeliveryClient keepalive settings for communication with ordering + # nodes. + deliveryClient: + # Interval is the time between pings to ordering nodes. This must + # greater than or equal to the minInterval specified by ordering + # nodes. + interval: 60s + # Timeout is the duration the client waits for a response from + # ordering nodes before closing the connection + timeout: 20s + + + # Gossip related configuration + gossip: + # Bootstrap set to initialize gossip with. + # This is a list of other peers that this peer reaches out to at startup. + # Important: The endpoints here have to be endpoints of peers in the same + # organization, because the peer would refuse connecting to these endpoints + # unless they are in the same organization as the peer. + bootstrap: 127.0.0.1:7051 + + # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. + # Setting both to true would result in the termination of the peer + # since this is undefined state. If the peers are configured with + # useLeaderElection=false, make sure there is at least 1 peer in the + # organization that its orgLeader is set to true. + + # Defines whenever peer will initialize dynamic algorithm for + # "leader" selection, where leader is the peer to establish + # connection with ordering service and use delivery protocol + # to pull ledger blocks from ordering service. + useLeaderElection: false + # Statically defines peer to be an organization "leader", + # where this means that current peer will maintain connection + # with ordering service and disseminate block across peers in + # its own organization. Multiple peers or all peers in an organization + # may be configured as org leaders, so that they all pull + # blocks directly from ordering service. + orgLeader: true + + # Interval for membershipTracker polling + membershipTrackerInterval: 5s + + # Overrides the endpoint that the peer publishes to peers + # in its organization. For peers in foreign organizations + # see 'externalEndpoint' + endpoint: + # Maximum count of blocks stored in memory + maxBlockCountToStore: 10 + # Max time between consecutive message pushes(unit: millisecond) + maxPropagationBurstLatency: 10ms + # Max number of messages stored until a push is triggered to remote peers + maxPropagationBurstSize: 10 + # Number of times a message is pushed to remote peers + propagateIterations: 1 + # Number of peers selected to push messages to + propagatePeerNum: 3 + # Determines frequency of pull phases(unit: second) + # Must be greater than digestWaitTime + responseWaitTime + pullInterval: 4s + # Number of peers to pull from + pullPeerNum: 3 + # Determines frequency of pulling state info messages from peers(unit: second) + requestStateInfoInterval: 4s + # Determines frequency of pushing state info messages to peers(unit: second) + publishStateInfoInterval: 4s + # Maximum time a stateInfo message is kept until expired + stateInfoRetentionInterval: + # Time from startup certificates are included in Alive messages(unit: second) + publishCertPeriod: 10s + # Should we skip verifying block messages or not (currently not in use) + skipBlockVerification: false + # Dial timeout(unit: second) + dialTimeout: 3s + # Connection timeout(unit: second) + connTimeout: 2s + # Buffer size of received messages + recvBuffSize: 20 + # Buffer size of sending messages + sendBuffSize: 200 + # Time to wait before pull engine processes incoming digests (unit: second) + # Should be slightly smaller than requestWaitTime + digestWaitTime: 1s + # Time to wait before pull engine removes incoming nonce (unit: milliseconds) + # Should be slightly bigger than digestWaitTime + requestWaitTime: 1500ms + # Time to wait before pull engine ends pull (unit: second) + responseWaitTime: 2s + # Alive check interval(unit: second) + aliveTimeInterval: 5s + # Alive expiration timeout(unit: second) + aliveExpirationTimeout: 25s + # Reconnect interval(unit: second) + reconnectInterval: 25s + # Max number of attempts to connect to a peer + maxConnectionAttempts: 120 + # Message expiration factor for alive messages + msgExpirationFactor: 20 + # This is an endpoint that is published to peers outside of the organization. + # If this isn't set, the peer will not be known to other organizations. + externalEndpoint: + # Leader election service configuration + election: + # Longest time peer waits for stable membership during leader election startup (unit: second) + startupGracePeriod: 15s + # Interval gossip membership samples to check its stability (unit: second) + membershipSampleInterval: 1s + # Time passes since last declaration message before peer decides to perform leader election (unit: second) + leaderAliveThreshold: 10s + # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) + leaderElectionDuration: 5s + + pvtData: + # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block + # would be attempted to be pulled from peers until the block would be committed without the private data + pullRetryThreshold: 60s + # As private data enters the transient store, it is associated with the peer's ledger's height at that time. + # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, + # and the private data residing inside the transient store that is guaranteed not to be purged. + # Private data is purged from the transient store when blocks with sequences that are multiples + # of transientstoreMaxBlockRetention are committed. + transientstoreMaxBlockRetention: 1000 + # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer + # at private data push at endorsement time. + pushAckTimeout: 3s + # Block to live pulling margin, used as a buffer + # to prevent peer from trying to pull private data + # from peers that is soon to be purged in next N blocks. + # This helps a newly joined peer catch up to current + # blockchain height quicker. + btlPullMargin: 10 + # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to + # pull from the other peers the most recent missing blocks with a maximum batch size limitation. + # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a + # single iteration. + reconcileBatchSize: 10 + # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning + # of the next reconciliation iteration. + reconcileSleepInterval: 1m + # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. + reconciliationEnabled: true + # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid + # transaction's private data from other peers need to be skipped during the commit time and pulled + # only through reconciler. + skipPullingInvalidTransactionsDuringCommit: false + # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. + # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values + # for disseminating private data. + # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to + # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. + implicitCollectionDisseminationPolicy: + # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully + # disseminate private data for its own implicit collection during endorsement. Default value is 0. + requiredPeerCount: 0 + # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to + # disseminate private data for its own implicit collection during endorsement. Default value is 1. + maxPeerCount: 1 + + # Gossip state transfer related configuration + state: + # indicates whenever state transfer is enabled or not + # default value is false, i.e. state transfer is active + # and takes care to sync up missing blocks allowing + # lagging peer to catch up to speed with rest network. + # Keep in mind that when peer.gossip.useLeaderElection is true + # and there are several peers in the organization, + # or peer.gossip.useLeaderElection is false alongside with + # peer.gossip.orgleader being false, the peer's ledger may lag behind + # the rest of the peers and will never catch up due to state transfer + # being disabled. + enabled: false + # checkInterval interval to check whether peer is lagging behind enough to + # request blocks via state transfer from another peer. + checkInterval: 10s + # responseTimeout amount of time to wait for state transfer response from + # other peers + responseTimeout: 3s + # batchSize the number of blocks to request via state transfer from another peer + batchSize: 10 + # blockBufferSize reflects the size of the re-ordering buffer + # which captures blocks and takes care to deliver them in order + # down to the ledger layer. The actual buffer size is bounded between + # 0 and 2*blockBufferSize, each channel maintains its own buffer + blockBufferSize: 20 + # maxRetries maximum number of re-tries to ask + # for single state transfer request + maxRetries: 3 + + # TLS Settings + tls: + # Require server-side TLS + enabled: false + # Require client certificates / mutual TLS for inbound connections. + # Note that clients that are not configured to use a certificate will + # fail to connect to the peer. + clientAuthRequired: false + # X.509 certificate used for TLS server + cert: + file: tls/server.crt + # Private key used for TLS server + key: + file: tls/server.key + # rootcert.file represents the trusted root certificate chain used for verifying certificates + # of other nodes during outbound connections. + # It is not required to be set, but can be used to augment the set of TLS CA certificates + # available from the MSPs of each channel’s configuration. + rootcert: + file: tls/ca.crt + # If mutual TLS is enabled, clientRootCAs.files contains a list of additional root certificates + # used for verifying certificates of client connections. + # It augments the set of TLS CA certificates available from the MSPs of each channel’s configuration. + # Minimally, set your organization's TLS CA root certificate so that the peer can receive join channel requests. + clientRootCAs: + files: + - tls/ca.crt + # Private key used for TLS when making client connections. + # If not set, peer.tls.key.file will be used instead + clientKey: + file: + # X.509 certificate used for TLS when making client connections. + # If not set, peer.tls.cert.file will be used instead + clientCert: + file: + + # Authentication contains configuration parameters related to authenticating + # client messages + authentication: + # the acceptable difference between the current server time and the + # client's time as specified in a client request message + timewindow: 15m + + # Path on the file system where peer will store data (eg ledger). This + # location must be access control protected to prevent unintended + # modification that might corrupt the peer operations. + fileSystemPath: /var/hyperledger/production + + # BCCSP (Blockchain crypto provider): Select which crypto implementation or + # library to use + BCCSP: + Default: SW + # Settings for the SW crypto provider (i.e. when DEFAULT: SW) + SW: + # TODO: The default Hash and Security level needs refactoring to be + # fully configurable. Changing these defaults requires coordination + # SHA2 is hardcoded in several places, not only BCCSP + Hash: SHA2 + Security: 256 + # Location of Key Store + FileKeyStore: + # If "", defaults to 'mspConfigPath'/keystore + KeyStore: + # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) + PKCS11: + # Location of the PKCS11 module library + Library: + # Token Label + Label: + # User PIN + Pin: + Hash: + Security: + + # Path on the file system where peer will find MSP local configurations + mspConfigPath: mspreally + # Identifier of the local MSP + # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- + # Deployers need to change the value of the localMspId string. + # In particular, the name of the local MSP ID of a peer needs + # to match the name of one of the MSPs in each of the channel + # that this peer is a member of. Otherwise this peer's messages + # will not be identified as valid by other nodes. + localMspId: SampleOrg + + # CLI common client config options + client: + # connection timeout + connTimeout: 3s + + # Delivery service related config + deliveryclient: + # Enables this peer to disseminate blocks it pulled from the ordering service + # via gossip. + # Note that 'gossip.state.enabled' controls point to point block replication + # of blocks committed in the past. + blockGossipEnabled: true + # It sets the total time the delivery service may spend in reconnection + # attempts until its retry logic gives up and returns an error + reconnectTotalTimeThreshold: 3600s + + # It sets the delivery service <-> ordering service node connection timeout + connTimeout: 3s + + # It sets the delivery service maximal delay between consecutive retries + reConnectBackoffThreshold: 3600s + + # A list of orderer endpoint addresses which should be overridden + # when found in channel configurations. + addressOverrides: + # - from: + # to: + # caCertsFile: + # - from: + # to: + # caCertsFile: + + # Type for the local MSP - by default it's of type bccsp + localMspType: bccsp + + # Used with Go profiling tools only in none production environment. In + # production, it should be disabled (eg enabled: false) + profile: + enabled: false + listenAddress: 0.0.0.0:6060 + + # Handlers defines custom handlers that can filter and mutate + # objects passing within the peer, such as: + # Auth filter - reject or forward proposals from clients + # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation + # Valid handler definition contains: + # - A name which is a factory method name defined in + # core/handlers/library/library.go for statically compiled handlers + # - library path to shared object binary for pluggable filters + # Auth filters and decorators are chained and executed in the order that + # they are defined. For example: + # authFilters: + # - + # name: FilterOne + # library: /opt/lib/filter.so + # - + # name: FilterTwo + # decorators: + # - + # name: DecoratorOne + # - + # name: DecoratorTwo + # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so + handlers: + authFilters: + - + name: DefaultAuth + - + name: ExpirationCheck # This filter checks identity x509 certificate expiration + decorators: + - + name: DefaultDecorator + endorsers: + escc: + name: DefaultEndorsement + library: + validators: + vscc: + name: DefaultValidation + library: + + # library: /etc/hyperledger/fabric/plugin/escc.so + # Number of goroutines that will execute transaction validation in parallel. + # By default, the peer chooses the number of CPUs on the machine. Set this + # variable to override that choice. + # NOTE: overriding this value might negatively influence the performance of + # the peer so please change this value only if you know what you're doing + validatorPoolSize: + + # The discovery service is used by clients to query information about peers, + # such as - which peers have joined a certain channel, what is the latest + # channel config, and most importantly - given a chaincode and a channel, + # what possible sets of peers satisfy the endorsement policy. + discovery: + enabled: true + # Whether the authentication cache is enabled or not. + authCacheEnabled: true + # The maximum size of the cache, after which a purge takes place + authCacheMaxSize: 1000 + # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation + authCachePurgeRetentionRatio: 0.75 + # Whether to allow non-admins to perform non channel scoped queries. + # When this is false, it means that only peer admins can perform non channel scoped queries. + orgMembersAllowedAccess: false + + # Limits is used to configure some internal resource limits. + limits: + # Concurrency limits the number of concurrently running requests to a service on each peer. + # Currently this option is only applied to endorser service and deliver service. + # When the property is missing or the value is 0, the concurrency limit is disabled for the service. + concurrency: + # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, + # including both user chaincodes and system chaincodes. + endorserService: 2500 + # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. + deliverService: 2500 + + # Since all nodes should be consistent it is recommended to keep + # the default value of 100MB for MaxRecvMsgSize & MaxSendMsgSize + # Max message size in bytes GRPC server and client can receive + maxRecvMsgSize: 104857600 + # Max message size in bytes GRPC server and client can send + maxSendMsgSize: 104857600 + +############################################################################### +# +# VM section +# +############################################################################### +#vm: + + # Endpoint of the vm management system. For docker can be one of the following in general + # unix:///var/run/docker.sock + # http://localhost:2375 + # https://localhost:2376 + # If you utilize external chaincode builders and don't need the default Docker chaincode builder, + # the endpoint should be unconfigured so that the peer's Docker health checker doesn't get registered. +# endpoint: unix:///var/run/docker.sock + + # settings for docker vms + # docker: + # tls: + # enabled: false + # ca: + # file: docker/ca.crt + # cert: + # file: docker/tls.crt + # key: + # file: docker/tls.key + + # Enables/disables the standard out/err from chaincode containers for + # debugging purposes + # attachStdout: false + + # Parameters on creating docker container. + # Container may be efficiently created using ipam & dns-server for cluster + # NetworkMode - sets the networking mode for the container. Supported + # standard values are: `host`(default),`bridge`,`ipvlan`,`none`. + # Dns - a list of DNS servers for the container to use. + # Note: `Privileged` `Binds` `Links` and `PortBindings` properties of + # Docker Host Config are not supported and will not be used if set. + # LogConfig - sets the logging driver (Type) and related options + # (Config) for Docker. For more info, + # https://docs.docker.com/engine/admin/logging/overview/ + # Note: Set LogConfig using Environment Variables is not supported. + # hostConfig: + # NetworkMode: host + # Dns: + # # - 192.168.0.1 + # LogConfig: + # Type: json-file + # Config: + # max-size: "50m" + # max-file: "5" + # Memory: 2147483648 + +############################################################################### +# +# Chaincode section +# +############################################################################### +chaincode: + + # The id is used by the Chaincode stub to register the executing Chaincode + # ID with the Peer and is generally supplied through ENV variables + # the `path` form of ID is provided when installing the chaincode. + # The `name` is used for all other requests and can be any string. + id: + path: + name: + + # Generic builder environment, suitable for most chaincode types + builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) + + # Enables/disables force pulling of the base docker images (listed below) + # during user chaincode instantiation. + # Useful when using moving image tags (such as :latest) + pull: false + + golang: + # golang will never need more than baseos + runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) + + # whether or not golang chaincode should be linked dynamically + dynamicLink: false + + java: + # This is an image based on java:openjdk-8 with addition compiler + # tools added for java shim layer packaging. + # This image is packed with shim layer libraries that are necessary + # for Java chaincode runtime. + runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) + + node: + # This is an image based on node:$(NODE_VER)-alpine + runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) + + # List of directories to treat as external builders and launchers for + # chaincode. The external builder detection processing will iterate over the + # builders in the order specified below. + # If you don't need to fallback to the default Docker builder, also unconfigure vm.endpoint above. + # To override this property via env variable use CORE_CHAINCODE_EXTERNALBUILDERS: [{name: x, path: dir1}, {name: y, path: dir2}] + externalBuilders: + - name: ccaas_builder + path: /opt/hyperledger/ccaas_builder + propagateEnvironment: + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG + + + # The maximum duration to wait for the chaincode build and install process + # to complete. + installTimeout: 300s + + # Timeout duration for starting up a container and waiting for Register + # to come through. + startuptimeout: 300s + + # Timeout duration for Invoke and Init calls to prevent runaway. + # This timeout is used by all chaincodes in all the channels, including + # system chaincodes. + # Note that during Invoke, if the image is not available (e.g. being + # cleaned up when in development environment), the peer will automatically + # build the image, which might take more time. In production environment, + # the chaincode image is unlikely to be deleted, so the timeout could be + # reduced accordingly. + executetimeout: 30s + + # There are 2 modes: "dev" and "net". + # In dev mode, user runs the chaincode after starting peer from + # command line on local machine. + # In net mode, peer will run chaincode in a docker container. + mode: net + + # keepalive in seconds. In situations where the communication goes through a + # proxy that does not support keep-alive, this parameter will maintain connection + # between peer and chaincode. + # A value <= 0 turns keepalive off + keepalive: 0 + + # enabled system chaincodes + system: + _lifecycle: enable + cscc: enable + lscc: enable + qscc: enable + + # Logging section for the chaincode container + logging: + # Default level for all loggers within the chaincode container + level: info + # Override default level for the 'shim' logger + shim: warning + # Format for the chaincode container logs + format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' + +############################################################################### +# +# Ledger section - ledger configuration encompasses both the blockchain +# and the state +# +############################################################################### +ledger: + + blockchain: + + state: + # stateDatabase - options are "goleveldb", "CouchDB" + # goleveldb - default state database stored in goleveldb. + # CouchDB - store state database in CouchDB + stateDatabase: goleveldb + # Limit on the number of records to return per query + totalQueryLimit: 100000 + couchDBConfig: + # It is recommended to run CouchDB on the same server as the peer, and + # not map the CouchDB container port to a server port in docker-compose. + # Otherwise proper security must be provided on the connection between + # CouchDB client (on the peer) and server. + couchDBAddress: 127.0.0.1:5984 + # This username must have read and write authority on CouchDB + username: + # The password is recommended to pass as an environment variable + # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). + # If it is stored here, the file must be access control protected + # to prevent unintended users from discovering the password. + password: + # Number of retries for CouchDB errors + maxRetries: 3 + # Number of retries for CouchDB errors during peer startup. + # The delay between retries doubles for each attempt. + # Default of 10 retries results in 11 attempts over 2 minutes. + maxRetriesOnStartup: 10 + # CouchDB request timeout (unit: duration, e.g. 20s) + requestTimeout: 35s + # Limit on the number of records per each CouchDB query + # Note that chaincode queries are only bound by totalQueryLimit. + # Internally the chaincode may execute multiple CouchDB queries, + # each of size internalQueryLimit. + internalQueryLimit: 1000 + # Limit on the number of records per CouchDB bulk update batch + maxBatchUpdateSize: 1000 + # Create the _global_changes system database + # This is optional. Creating the global changes database will require + # additional system resources to track changes and maintain the database + createGlobalChangesDB: false + # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state + # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple + # of 32 MB, the peer would round the size to the next multiple of 32 MB. + # To disable the cache, 0 MB needs to be assigned to the cacheSize. + cacheSize: 64 + + history: + # enableHistoryDatabase - options are true or false + # Indicates if the history of key updates should be stored. + # All history 'index' will be stored in goleveldb, regardless if using + # CouchDB or alternate database for the state. + enableHistoryDatabase: true + + pvtdataStore: + # the maximum db batch size for converting + # the ineligible missing data entries to eligible missing data entries + collElgProcMaxDbBatchSize: 5000 + # the minimum duration (in milliseconds) between writing + # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries + collElgProcDbBatchesInterval: 1000 + # The missing data entries are classified into two categories: + # (1) prioritized + # (2) deprioritized + # Initially, all missing data are in the prioritized list. When the + # reconciler is unable to fetch the missing data from other peers, + # the unreconciled missing data would be moved to the deprioritized list. + # The reconciler would retry deprioritized missing data after every + # deprioritizedDataReconcilerInterval (unit: minutes). Note that the + # interval needs to be greater than the reconcileSleepInterval + deprioritizedDataReconcilerInterval: 60m + + snapshots: + # Path on the file system where peer will store ledger snapshots + rootDir: /var/hyperledger/production/snapshots + +############################################################################### +# +# Operations section +# +############################################################################### +operations: + # host and port for the operations server + listenAddress: 127.0.0.1:9443 + + # 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: + + # most operations service endpoints require client authentication when TLS + # is enabled. clientAuthRequired requires client certificate authentication + # at the TLS layer to access all resources. + clientAuthRequired: false + + # paths to PEM encoded ca certificates to trust for client authentication + clientRootCAs: + files: [] + +############################################################################### +# +# Metrics section +# +############################################################################### +metrics: + # metrics provider is one of 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 pushed + # to statsd; timings are pushed immediately + writeInterval: 10s + + # prefix is prepended to all emitted statsd metrics + prefix: diff --git a/test-network/addOrg3/compose/podman/podman-compose-ca-org3.yaml b/test-network/addOrg3/compose/podman/podman-compose-ca-org3.yaml new file mode 100644 index 00000000..74dbf4d6 --- /dev/null +++ b/test-network/addOrg3/compose/podman/podman-compose-ca-org3.yaml @@ -0,0 +1,27 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + +networks: + test: + name: fabric_test + +services: + ca_org3: + image: hyperledger/fabric-ca:latest + labels: + service: hyperledger-fabric + environment: + - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server + - FABRIC_CA_SERVER_CA_NAME=ca-org3 + - FABRIC_CA_SERVER_TLS_ENABLED=true + - FABRIC_CA_SERVER_PORT=11054 + ports: + - "11054:11054" + command: sh -c 'fabric-ca-server start -b admin:adminpw -d' + volumes: + - ../fabric-ca/org3:/etc/hyperledger/fabric-ca-server + container_name: ca_org3 diff --git a/test-network/addOrg3/compose/podman/podman-compose-couch-org3.yaml b/test-network/addOrg3/compose/podman/podman-compose-couch-org3.yaml new file mode 100644 index 00000000..d10766f3 --- /dev/null +++ b/test-network/addOrg3/compose/podman/podman-compose-couch-org3.yaml @@ -0,0 +1,42 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + +networks: + test: + name: fabric_test + +services: + couchdb4: + container_name: couchdb4 + image: couchdb:3.1.1 + labels: + service: hyperledger-fabric + # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password + # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. + environment: + - COUCHDB_USER=admin + - COUCHDB_PASSWORD=adminpw + # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, + # for example map it to utilize Fauxton User Interface in dev environments. + ports: + - "9984:5984" + networks: + - test + + peer0.org3.example.com: + environment: + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb4:5984 + # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD + # provide the credentials for ledger to connect to CouchDB. The username and password must + # match the username and password set for the associated CouchDB. + - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin + - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw + depends_on: + - couchdb4 + networks: + - test diff --git a/test-network/addOrg3/compose/podman/podman-compose-org3.yaml b/test-network/addOrg3/compose/podman/podman-compose-org3.yaml new file mode 100644 index 00000000..208f6d04 --- /dev/null +++ b/test-network/addOrg3/compose/podman/podman-compose-org3.yaml @@ -0,0 +1,51 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + +volumes: + peer0.org3.example.com: + +networks: + test: + name: fabric_test + +services: + + peer0.org3.example.com: + container_name: peer0.org3.example.com + image: hyperledger/fabric-peer:latest + labels: + service: hyperledger-fabric + environment: + - FABRIC_CFG_PATH=/etc/hyperledger/peercfg + #Generic peer variables + - FABRIC_LOGGING_SPEC=INFO + #- FABRIC_LOGGING_SPEC=DEBUG + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_PROFILE_ENABLED=true + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt + # Peer specific variables + - CORE_PEER_ID=peer0.org3.example.com + - CORE_PEER_ADDRESS=peer0.org3.example.com:11051 + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp + - CORE_PEER_LISTENADDRESS=0.0.0.0:11051 + - CORE_PEER_CHAINCODEADDRESS=peer0.org3.example.com:11052 + - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:11052 + - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org3.example.com:11051 + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051 + - CORE_PEER_LOCALMSPID=Org3MSP + volumes: + - ../peercfg:/etc/hyperledger/peercfg + - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com:/etc/hyperledger/fabric + - peer0.org3.example.com:/var/hyperledger/production + working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer + command: peer node start + ports: + - 11051:11051 + networks: + - test diff --git a/test-network/docker/docker-compose-ca.yaml b/test-network/compose/compose-ca.yaml similarity index 100% rename from test-network/docker/docker-compose-ca.yaml rename to test-network/compose/compose-ca.yaml diff --git a/test-network/docker/docker-compose-couch.yaml b/test-network/compose/compose-couch.yaml similarity index 100% rename from test-network/docker/docker-compose-couch.yaml rename to test-network/compose/compose-couch.yaml diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/compose/compose-test-net.yaml similarity index 83% rename from test-network/docker/docker-compose-test-net.yaml rename to test-network/compose/compose-test-net.yaml index 059f5838..6e0aef26 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/compose/compose-test-net.yaml @@ -45,10 +45,9 @@ services: - ORDERER_ADMIN_LISTENADDRESS=0.0.0.0:7053 - ORDERER_OPERATIONS_LISTENADDRESS=orderer.example.com:9443 - ORDERER_METRICS_PROVIDER=prometheus - working_dir: /opt/gopath/src/github.com/hyperledger/fabric + working_dir: /root command: orderer volumes: - - ../system-genesis-block/genesis.block:/var/hyperledger/orderer/orderer.genesis.block - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls - orderer.example.com:/var/hyperledger/production/orderer @@ -65,9 +64,7 @@ services: labels: service: hyperledger-fabric environment: - #Generic peer variables - - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test + - FABRIC_CFG_PATH=/etc/hyperledger/peercfg - FABRIC_LOGGING_SPEC=INFO #- FABRIC_LOGGING_SPEC=DEBUG - CORE_PEER_TLS_ENABLED=true @@ -84,16 +81,15 @@ services: - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.example.com:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051 - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp - CORE_OPERATIONS_LISTENADDRESS=peer0.org1.example.com:9444 - CORE_METRICS_PROVIDER=prometheus - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG={"peername":"peer0org1"} - CORE_CHAINCODE_EXECUTETIMEOUT=300s volumes: - - ${DOCKER_SOCK}:/host/var/run/docker.sock - - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp - - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls + - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com:/etc/hyperledger/fabric - peer0.org1.example.com:/var/hyperledger/production - working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer + working_dir: /root command: peer node start ports: - 7051:7051 @@ -107,9 +103,7 @@ services: labels: service: hyperledger-fabric environment: - #Generic peer variables - - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test + - FABRIC_CFG_PATH=/etc/hyperledger/peercfg - FABRIC_LOGGING_SPEC=INFO #- FABRIC_LOGGING_SPEC=DEBUG - CORE_PEER_TLS_ENABLED=true @@ -126,16 +120,15 @@ services: - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:9051 - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:9051 - CORE_PEER_LOCALMSPID=Org2MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp - CORE_OPERATIONS_LISTENADDRESS=peer0.org2.example.com:9445 - CORE_METRICS_PROVIDER=prometheus - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG={"peername":"peer0org2"} - CORE_CHAINCODE_EXECUTETIMEOUT=300s volumes: - - ${DOCKER_SOCK}:/host/var/run/docker.sock - - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp - - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls + - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com:/etc/hyperledger/fabric - peer0.org2.example.com:/var/hyperledger/production - working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer + working_dir: /root command: peer node start ports: - 9051:9051 @@ -152,8 +145,8 @@ services: stdin_open: true environment: - GOPATH=/opt/gopath - - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - FABRIC_LOGGING_SPEC=INFO + - FABRIC_CFG_PATH=/etc/hyperledger/peercfg #- FABRIC_LOGGING_SPEC=DEBUG working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash diff --git a/test-network/compose/docker/docker-compose-ca.yaml b/test-network/compose/docker/docker-compose-ca.yaml new file mode 100644 index 00000000..16732f0c --- /dev/null +++ b/test-network/compose/docker/docker-compose-ca.yaml @@ -0,0 +1,7 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + diff --git a/test-network/compose/docker/docker-compose-couch.yaml b/test-network/compose/docker/docker-compose-couch.yaml new file mode 100644 index 00000000..6ab883d4 --- /dev/null +++ b/test-network/compose/docker/docker-compose-couch.yaml @@ -0,0 +1,6 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' diff --git a/test-network/compose/docker/docker-compose-test-net.yaml b/test-network/compose/docker/docker-compose-test-net.yaml new file mode 100644 index 00000000..68f6dc56 --- /dev/null +++ b/test-network/compose/docker/docker-compose-test-net.yaml @@ -0,0 +1,38 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' +services: + peer0.org1.example.com: + container_name: peer0.org1.example.com + image: hyperledger/fabric-peer:latest + labels: + service: hyperledger-fabric + environment: + #Generic peer variables + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test + volumes: + - ./docker/peercfg:/etc/hyperledger/peercfg + - ${DOCKER_SOCK}:/host/var/run/docker.sock + + peer0.org2.example.com: + container_name: peer0.org2.example.com + image: hyperledger/fabric-peer:latest + labels: + service: hyperledger-fabric + environment: + #Generic peer variables + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test + volumes: + - ./docker/peercfg:/etc/hyperledger/peercfg + - ${DOCKER_SOCK}:/host/var/run/docker.sock + + cli: + container_name: cli + image: hyperledger/fabric-tools:latest + volumes: + - ./docker/peercfg:/etc/hyperledger/peercfg diff --git a/test-network/compose/docker/peercfg/core.yaml b/test-network/compose/docker/peercfg/core.yaml new file mode 100644 index 00000000..16e5b606 --- /dev/null +++ b/test-network/compose/docker/peercfg/core.yaml @@ -0,0 +1,777 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################### +# +# Peer section +# +############################################################################### +peer: + + # The peer id provides a name for this peer instance and is used when + # naming docker resources. + id: jdoe + + # The networkId allows for logical separation of networks and is used when + # naming docker resources. + networkId: dev + + # The Address at local network interface this Peer will listen on. + # By default, it will listen on all network interfaces + listenAddress: 0.0.0.0:7051 + + # The endpoint this peer uses to listen for inbound chaincode connections. + # If this is commented-out, the listen address is selected to be + # the peer's address (see below) with port 7052 + # chaincodeListenAddress: 0.0.0.0:7052 + + # The endpoint the chaincode for this peer uses to connect to the peer. + # If this is not specified, the chaincodeListenAddress address is selected. + # And if chaincodeListenAddress is not specified, address is selected from + # peer address (see below). If specified peer address is invalid then it + # will fallback to the auto detected IP (local IP) regardless of the peer + # addressAutoDetect value. + # chaincodeAddress: 0.0.0.0:7052 + + # When used as peer config, this represents the endpoint to other peers + # in the same organization. For peers in other organization, see + # gossip.externalEndpoint for more info. + # When used as CLI config, this means the peer's endpoint to interact with + address: 0.0.0.0:7051 + + # Whether the Peer should programmatically determine its address + # This case is useful for docker containers. + # When set to true, will override peer address. + addressAutoDetect: false + + # Settings for the Peer's gateway server. + gateway: + # Whether the gateway is enabled for this Peer. + enabled: true + # endorsementTimeout is the duration the gateway waits for a response + # from other endorsing peers before returning a timeout error to the client. + endorsementTimeout: 30s + # dialTimeout is the duration the gateway waits for a connection + # to other network nodes. + dialTimeout: 2m + + + # Keepalive settings for peer server and clients + keepalive: + # Interval is the duration after which if the server does not see + # any activity from the client it pings the client to see if it's alive + interval: 7200s + # Timeout is the duration the server waits for a response + # from the client after sending a ping before closing the connection + timeout: 20s + # MinInterval is the minimum permitted time between client pings. + # If clients send pings more frequently, the peer server will + # disconnect them + minInterval: 60s + # Client keepalive settings for communicating with other peer nodes + client: + # Interval is the time between pings to peer nodes. This must + # greater than or equal to the minInterval specified by peer + # nodes + interval: 60s + # Timeout is the duration the client waits for a response from + # peer nodes before closing the connection + timeout: 20s + # DeliveryClient keepalive settings for communication with ordering + # nodes. + deliveryClient: + # Interval is the time between pings to ordering nodes. This must + # greater than or equal to the minInterval specified by ordering + # nodes. + interval: 60s + # Timeout is the duration the client waits for a response from + # ordering nodes before closing the connection + timeout: 20s + + + # Gossip related configuration + gossip: + # Bootstrap set to initialize gossip with. + # This is a list of other peers that this peer reaches out to at startup. + # Important: The endpoints here have to be endpoints of peers in the same + # organization, because the peer would refuse connecting to these endpoints + # unless they are in the same organization as the peer. + bootstrap: 127.0.0.1:7051 + + # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. + # Setting both to true would result in the termination of the peer + # since this is undefined state. If the peers are configured with + # useLeaderElection=false, make sure there is at least 1 peer in the + # organization that its orgLeader is set to true. + + # Defines whenever peer will initialize dynamic algorithm for + # "leader" selection, where leader is the peer to establish + # connection with ordering service and use delivery protocol + # to pull ledger blocks from ordering service. + useLeaderElection: false + # Statically defines peer to be an organization "leader", + # where this means that current peer will maintain connection + # with ordering service and disseminate block across peers in + # its own organization. Multiple peers or all peers in an organization + # may be configured as org leaders, so that they all pull + # blocks directly from ordering service. + orgLeader: true + + # Interval for membershipTracker polling + membershipTrackerInterval: 5s + + # Overrides the endpoint that the peer publishes to peers + # in its organization. For peers in foreign organizations + # see 'externalEndpoint' + endpoint: + # Maximum count of blocks stored in memory + maxBlockCountToStore: 10 + # Max time between consecutive message pushes(unit: millisecond) + maxPropagationBurstLatency: 10ms + # Max number of messages stored until a push is triggered to remote peers + maxPropagationBurstSize: 10 + # Number of times a message is pushed to remote peers + propagateIterations: 1 + # Number of peers selected to push messages to + propagatePeerNum: 3 + # Determines frequency of pull phases(unit: second) + # Must be greater than digestWaitTime + responseWaitTime + pullInterval: 4s + # Number of peers to pull from + pullPeerNum: 3 + # Determines frequency of pulling state info messages from peers(unit: second) + requestStateInfoInterval: 4s + # Determines frequency of pushing state info messages to peers(unit: second) + publishStateInfoInterval: 4s + # Maximum time a stateInfo message is kept until expired + stateInfoRetentionInterval: + # Time from startup certificates are included in Alive messages(unit: second) + publishCertPeriod: 10s + # Should we skip verifying block messages or not (currently not in use) + skipBlockVerification: false + # Dial timeout(unit: second) + dialTimeout: 3s + # Connection timeout(unit: second) + connTimeout: 2s + # Buffer size of received messages + recvBuffSize: 20 + # Buffer size of sending messages + sendBuffSize: 200 + # Time to wait before pull engine processes incoming digests (unit: second) + # Should be slightly smaller than requestWaitTime + digestWaitTime: 1s + # Time to wait before pull engine removes incoming nonce (unit: milliseconds) + # Should be slightly bigger than digestWaitTime + requestWaitTime: 1500ms + # Time to wait before pull engine ends pull (unit: second) + responseWaitTime: 2s + # Alive check interval(unit: second) + aliveTimeInterval: 5s + # Alive expiration timeout(unit: second) + aliveExpirationTimeout: 25s + # Reconnect interval(unit: second) + reconnectInterval: 25s + # Max number of attempts to connect to a peer + maxConnectionAttempts: 120 + # Message expiration factor for alive messages + msgExpirationFactor: 20 + # This is an endpoint that is published to peers outside of the organization. + # If this isn't set, the peer will not be known to other organizations. + externalEndpoint: + # Leader election service configuration + election: + # Longest time peer waits for stable membership during leader election startup (unit: second) + startupGracePeriod: 15s + # Interval gossip membership samples to check its stability (unit: second) + membershipSampleInterval: 1s + # Time passes since last declaration message before peer decides to perform leader election (unit: second) + leaderAliveThreshold: 10s + # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) + leaderElectionDuration: 5s + + pvtData: + # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block + # would be attempted to be pulled from peers until the block would be committed without the private data + pullRetryThreshold: 60s + # As private data enters the transient store, it is associated with the peer's ledger's height at that time. + # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, + # and the private data residing inside the transient store that is guaranteed not to be purged. + # Private data is purged from the transient store when blocks with sequences that are multiples + # of transientstoreMaxBlockRetention are committed. + transientstoreMaxBlockRetention: 1000 + # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer + # at private data push at endorsement time. + pushAckTimeout: 3s + # Block to live pulling margin, used as a buffer + # to prevent peer from trying to pull private data + # from peers that is soon to be purged in next N blocks. + # This helps a newly joined peer catch up to current + # blockchain height quicker. + btlPullMargin: 10 + # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to + # pull from the other peers the most recent missing blocks with a maximum batch size limitation. + # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a + # single iteration. + reconcileBatchSize: 10 + # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning + # of the next reconciliation iteration. + reconcileSleepInterval: 1m + # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. + reconciliationEnabled: true + # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid + # transaction's private data from other peers need to be skipped during the commit time and pulled + # only through reconciler. + skipPullingInvalidTransactionsDuringCommit: false + # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. + # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values + # for disseminating private data. + # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to + # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. + implicitCollectionDisseminationPolicy: + # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully + # disseminate private data for its own implicit collection during endorsement. Default value is 0. + requiredPeerCount: 0 + # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to + # disseminate private data for its own implicit collection during endorsement. Default value is 1. + maxPeerCount: 1 + + # Gossip state transfer related configuration + state: + # indicates whenever state transfer is enabled or not + # default value is false, i.e. state transfer is active + # and takes care to sync up missing blocks allowing + # lagging peer to catch up to speed with rest network. + # Keep in mind that when peer.gossip.useLeaderElection is true + # and there are several peers in the organization, + # or peer.gossip.useLeaderElection is false alongside with + # peer.gossip.orgleader being false, the peer's ledger may lag behind + # the rest of the peers and will never catch up due to state transfer + # being disabled. + enabled: false + # checkInterval interval to check whether peer is lagging behind enough to + # request blocks via state transfer from another peer. + checkInterval: 10s + # responseTimeout amount of time to wait for state transfer response from + # other peers + responseTimeout: 3s + # batchSize the number of blocks to request via state transfer from another peer + batchSize: 10 + # blockBufferSize reflects the size of the re-ordering buffer + # which captures blocks and takes care to deliver them in order + # down to the ledger layer. The actual buffer size is bounded between + # 0 and 2*blockBufferSize, each channel maintains its own buffer + blockBufferSize: 20 + # maxRetries maximum number of re-tries to ask + # for single state transfer request + maxRetries: 3 + + # TLS Settings + tls: + # Require server-side TLS + enabled: false + # Require client certificates / mutual TLS for inbound connections. + # Note that clients that are not configured to use a certificate will + # fail to connect to the peer. + clientAuthRequired: false + # X.509 certificate used for TLS server + cert: + file: tls/server.crt + # Private key used for TLS server + key: + file: tls/server.key + # rootcert.file represents the trusted root certificate chain used for verifying certificates + # of other nodes during outbound connections. + # It is not required to be set, but can be used to augment the set of TLS CA certificates + # available from the MSPs of each channel’s configuration. + rootcert: + file: tls/ca.crt + # If mutual TLS is enabled, clientRootCAs.files contains a list of additional root certificates + # used for verifying certificates of client connections. + # It augments the set of TLS CA certificates available from the MSPs of each channel’s configuration. + # Minimally, set your organization's TLS CA root certificate so that the peer can receive join channel requests. + clientRootCAs: + files: + - tls/ca.crt + # Private key used for TLS when making client connections. + # If not set, peer.tls.key.file will be used instead + clientKey: + file: + # X.509 certificate used for TLS when making client connections. + # If not set, peer.tls.cert.file will be used instead + clientCert: + file: + + # Authentication contains configuration parameters related to authenticating + # client messages + authentication: + # the acceptable difference between the current server time and the + # client's time as specified in a client request message + timewindow: 15m + + # Path on the file system where peer will store data (eg ledger). This + # location must be access control protected to prevent unintended + # modification that might corrupt the peer operations. + fileSystemPath: /var/hyperledger/production + + # BCCSP (Blockchain crypto provider): Select which crypto implementation or + # library to use + BCCSP: + Default: SW + # Settings for the SW crypto provider (i.e. when DEFAULT: SW) + SW: + # TODO: The default Hash and Security level needs refactoring to be + # fully configurable. Changing these defaults requires coordination + # SHA2 is hardcoded in several places, not only BCCSP + Hash: SHA2 + Security: 256 + # Location of Key Store + FileKeyStore: + # If "", defaults to 'mspConfigPath'/keystore + KeyStore: + # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) + PKCS11: + # Location of the PKCS11 module library + Library: + # Token Label + Label: + # User PIN + Pin: + Hash: + Security: + + # Path on the file system where peer will find MSP local configurations + mspConfigPath: mspreally + # Identifier of the local MSP + # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- + # Deployers need to change the value of the localMspId string. + # In particular, the name of the local MSP ID of a peer needs + # to match the name of one of the MSPs in each of the channel + # that this peer is a member of. Otherwise this peer's messages + # will not be identified as valid by other nodes. + localMspId: SampleOrg + + # CLI common client config options + client: + # connection timeout + connTimeout: 3s + + # Delivery service related config + deliveryclient: + # Enables this peer to disseminate blocks it pulled from the ordering service + # via gossip. + # Note that 'gossip.state.enabled' controls point to point block replication + # of blocks committed in the past. + blockGossipEnabled: true + # It sets the total time the delivery service may spend in reconnection + # attempts until its retry logic gives up and returns an error + reconnectTotalTimeThreshold: 3600s + + # It sets the delivery service <-> ordering service node connection timeout + connTimeout: 3s + + # It sets the delivery service maximal delay between consecutive retries + reConnectBackoffThreshold: 3600s + + # A list of orderer endpoint addresses which should be overridden + # when found in channel configurations. + addressOverrides: + # - from: + # to: + # caCertsFile: + # - from: + # to: + # caCertsFile: + + # Type for the local MSP - by default it's of type bccsp + localMspType: bccsp + + # Used with Go profiling tools only in none production environment. In + # production, it should be disabled (eg enabled: false) + profile: + enabled: false + listenAddress: 0.0.0.0:6060 + + # Handlers defines custom handlers that can filter and mutate + # objects passing within the peer, such as: + # Auth filter - reject or forward proposals from clients + # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation + # Valid handler definition contains: + # - A name which is a factory method name defined in + # core/handlers/library/library.go for statically compiled handlers + # - library path to shared object binary for pluggable filters + # Auth filters and decorators are chained and executed in the order that + # they are defined. For example: + # authFilters: + # - + # name: FilterOne + # library: /opt/lib/filter.so + # - + # name: FilterTwo + # decorators: + # - + # name: DecoratorOne + # - + # name: DecoratorTwo + # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so + handlers: + authFilters: + - + name: DefaultAuth + - + name: ExpirationCheck # This filter checks identity x509 certificate expiration + decorators: + - + name: DefaultDecorator + endorsers: + escc: + name: DefaultEndorsement + library: + validators: + vscc: + name: DefaultValidation + library: + + # library: /etc/hyperledger/fabric/plugin/escc.so + # Number of goroutines that will execute transaction validation in parallel. + # By default, the peer chooses the number of CPUs on the machine. Set this + # variable to override that choice. + # NOTE: overriding this value might negatively influence the performance of + # the peer so please change this value only if you know what you're doing + validatorPoolSize: + + # The discovery service is used by clients to query information about peers, + # such as - which peers have joined a certain channel, what is the latest + # channel config, and most importantly - given a chaincode and a channel, + # what possible sets of peers satisfy the endorsement policy. + discovery: + enabled: true + # Whether the authentication cache is enabled or not. + authCacheEnabled: true + # The maximum size of the cache, after which a purge takes place + authCacheMaxSize: 1000 + # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation + authCachePurgeRetentionRatio: 0.75 + # Whether to allow non-admins to perform non channel scoped queries. + # When this is false, it means that only peer admins can perform non channel scoped queries. + orgMembersAllowedAccess: false + + # Limits is used to configure some internal resource limits. + limits: + # Concurrency limits the number of concurrently running requests to a service on each peer. + # Currently this option is only applied to endorser service and deliver service. + # When the property is missing or the value is 0, the concurrency limit is disabled for the service. + concurrency: + # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, + # including both user chaincodes and system chaincodes. + endorserService: 2500 + # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. + deliverService: 2500 + + # Since all nodes should be consistent it is recommended to keep + # the default value of 100MB for MaxRecvMsgSize & MaxSendMsgSize + # Max message size in bytes GRPC server and client can receive + maxRecvMsgSize: 104857600 + # Max message size in bytes GRPC server and client can send + maxSendMsgSize: 104857600 + +############################################################################### +# +# VM section +# +############################################################################### +vm: + + # Endpoint of the vm management system. For docker can be one of the following in general + # unix:///var/run/docker.sock + # http://localhost:2375 + # https://localhost:2376 + # If you utilize external chaincode builders and don't need the default Docker chaincode builder, + # the endpoint should be unconfigured so that the peer's Docker health checker doesn't get registered. + endpoint: unix:///var/run/docker.sock + + # settings for docker vms + docker: + tls: + enabled: false + ca: + file: docker/ca.crt + cert: + file: docker/tls.crt + key: + file: docker/tls.key + + # Enables/disables the standard out/err from chaincode containers for + # debugging purposes + attachStdout: false + + # Parameters on creating docker container. + # Container may be efficiently created using ipam & dns-server for cluster + # NetworkMode - sets the networking mode for the container. Supported + # standard values are: `host`(default),`bridge`,`ipvlan`,`none`. + # Dns - a list of DNS servers for the container to use. + # Note: `Privileged` `Binds` `Links` and `PortBindings` properties of + # Docker Host Config are not supported and will not be used if set. + # LogConfig - sets the logging driver (Type) and related options + # (Config) for Docker. For more info, + # https://docs.docker.com/engine/admin/logging/overview/ + # Note: Set LogConfig using Environment Variables is not supported. + hostConfig: + NetworkMode: host + Dns: + # - 192.168.0.1 + LogConfig: + Type: json-file + Config: + max-size: "50m" + max-file: "5" + Memory: 2147483648 + +############################################################################### +# +# Chaincode section +# +############################################################################### +chaincode: + + # The id is used by the Chaincode stub to register the executing Chaincode + # ID with the Peer and is generally supplied through ENV variables + # the `path` form of ID is provided when installing the chaincode. + # The `name` is used for all other requests and can be any string. + id: + path: + name: + + # Generic builder environment, suitable for most chaincode types + builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) + + # Enables/disables force pulling of the base docker images (listed below) + # during user chaincode instantiation. + # Useful when using moving image tags (such as :latest) + pull: false + + golang: + # golang will never need more than baseos + runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) + + # whether or not golang chaincode should be linked dynamically + dynamicLink: false + + java: + # This is an image based on java:openjdk-8 with addition compiler + # tools added for java shim layer packaging. + # This image is packed with shim layer libraries that are necessary + # for Java chaincode runtime. + runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) + + node: + # This is an image based on node:$(NODE_VER)-alpine + runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) + + # List of directories to treat as external builders and launchers for + # chaincode. The external builder detection processing will iterate over the + # builders in the order specified below. + # If you don't need to fallback to the default Docker builder, also unconfigure vm.endpoint above. + # To override this property via env variable use CORE_CHAINCODE_EXTERNALBUILDERS: [{name: x, path: dir1}, {name: y, path: dir2}] + externalBuilders: + - name: ccaas_builder + path: /opt/hyperledger/ccaas_builder + propagateEnvironment: + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG + + + # The maximum duration to wait for the chaincode build and install process + # to complete. + installTimeout: 300s + + # Timeout duration for starting up a container and waiting for Register + # to come through. + startuptimeout: 300s + + # Timeout duration for Invoke and Init calls to prevent runaway. + # This timeout is used by all chaincodes in all the channels, including + # system chaincodes. + # Note that during Invoke, if the image is not available (e.g. being + # cleaned up when in development environment), the peer will automatically + # build the image, which might take more time. In production environment, + # the chaincode image is unlikely to be deleted, so the timeout could be + # reduced accordingly. + executetimeout: 30s + + # There are 2 modes: "dev" and "net". + # In dev mode, user runs the chaincode after starting peer from + # command line on local machine. + # In net mode, peer will run chaincode in a docker container. + mode: net + + # keepalive in seconds. In situations where the communication goes through a + # proxy that does not support keep-alive, this parameter will maintain connection + # between peer and chaincode. + # A value <= 0 turns keepalive off + keepalive: 0 + + # enabled system chaincodes + system: + _lifecycle: enable + cscc: enable + lscc: enable + qscc: enable + + # Logging section for the chaincode container + logging: + # Default level for all loggers within the chaincode container + level: info + # Override default level for the 'shim' logger + shim: warning + # Format for the chaincode container logs + format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' + +############################################################################### +# +# Ledger section - ledger configuration encompasses both the blockchain +# and the state +# +############################################################################### +ledger: + + blockchain: + + state: + # stateDatabase - options are "goleveldb", "CouchDB" + # goleveldb - default state database stored in goleveldb. + # CouchDB - store state database in CouchDB + stateDatabase: goleveldb + # Limit on the number of records to return per query + totalQueryLimit: 100000 + couchDBConfig: + # It is recommended to run CouchDB on the same server as the peer, and + # not map the CouchDB container port to a server port in docker-compose. + # Otherwise proper security must be provided on the connection between + # CouchDB client (on the peer) and server. + couchDBAddress: 127.0.0.1:5984 + # This username must have read and write authority on CouchDB + username: + # The password is recommended to pass as an environment variable + # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). + # If it is stored here, the file must be access control protected + # to prevent unintended users from discovering the password. + password: + # Number of retries for CouchDB errors + maxRetries: 3 + # Number of retries for CouchDB errors during peer startup. + # The delay between retries doubles for each attempt. + # Default of 10 retries results in 11 attempts over 2 minutes. + maxRetriesOnStartup: 10 + # CouchDB request timeout (unit: duration, e.g. 20s) + requestTimeout: 35s + # Limit on the number of records per each CouchDB query + # Note that chaincode queries are only bound by totalQueryLimit. + # Internally the chaincode may execute multiple CouchDB queries, + # each of size internalQueryLimit. + internalQueryLimit: 1000 + # Limit on the number of records per CouchDB bulk update batch + maxBatchUpdateSize: 1000 + # Create the _global_changes system database + # This is optional. Creating the global changes database will require + # additional system resources to track changes and maintain the database + createGlobalChangesDB: false + # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state + # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple + # of 32 MB, the peer would round the size to the next multiple of 32 MB. + # To disable the cache, 0 MB needs to be assigned to the cacheSize. + cacheSize: 64 + + history: + # enableHistoryDatabase - options are true or false + # Indicates if the history of key updates should be stored. + # All history 'index' will be stored in goleveldb, regardless if using + # CouchDB or alternate database for the state. + enableHistoryDatabase: true + + pvtdataStore: + # the maximum db batch size for converting + # the ineligible missing data entries to eligible missing data entries + collElgProcMaxDbBatchSize: 5000 + # the minimum duration (in milliseconds) between writing + # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries + collElgProcDbBatchesInterval: 1000 + # The missing data entries are classified into two categories: + # (1) prioritized + # (2) deprioritized + # Initially, all missing data are in the prioritized list. When the + # reconciler is unable to fetch the missing data from other peers, + # the unreconciled missing data would be moved to the deprioritized list. + # The reconciler would retry deprioritized missing data after every + # deprioritizedDataReconcilerInterval (unit: minutes). Note that the + # interval needs to be greater than the reconcileSleepInterval + deprioritizedDataReconcilerInterval: 60m + + snapshots: + # Path on the file system where peer will store ledger snapshots + rootDir: /var/hyperledger/production/snapshots + +############################################################################### +# +# Operations section +# +############################################################################### +operations: + # host and port for the operations server + listenAddress: 127.0.0.1:9443 + + # 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: + + # most operations service endpoints require client authentication when TLS + # is enabled. clientAuthRequired requires client certificate authentication + # at the TLS layer to access all resources. + clientAuthRequired: false + + # paths to PEM encoded ca certificates to trust for client authentication + clientRootCAs: + files: [] + +############################################################################### +# +# Metrics section +# +############################################################################### +metrics: + # metrics provider is one of 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 pushed + # to statsd; timings are pushed immediately + writeInterval: 10s + + # prefix is prepended to all emitted statsd metrics + prefix: diff --git a/test-network/compose/podman/peercfg/core.yaml b/test-network/compose/podman/peercfg/core.yaml new file mode 100644 index 00000000..4b173e63 --- /dev/null +++ b/test-network/compose/podman/peercfg/core.yaml @@ -0,0 +1,777 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################### +# +# Peer section +# +############################################################################### +peer: + + # The peer id provides a name for this peer instance and is used when + # naming docker resources. + id: jdoe + + # The networkId allows for logical separation of networks and is used when + # naming docker resources. + networkId: dev + + # The Address at local network interface this Peer will listen on. + # By default, it will listen on all network interfaces + listenAddress: 0.0.0.0:7051 + + # The endpoint this peer uses to listen for inbound chaincode connections. + # If this is commented-out, the listen address is selected to be + # the peer's address (see below) with port 7052 + # chaincodeListenAddress: 0.0.0.0:7052 + + # The endpoint the chaincode for this peer uses to connect to the peer. + # If this is not specified, the chaincodeListenAddress address is selected. + # And if chaincodeListenAddress is not specified, address is selected from + # peer address (see below). If specified peer address is invalid then it + # will fallback to the auto detected IP (local IP) regardless of the peer + # addressAutoDetect value. + # chaincodeAddress: 0.0.0.0:7052 + + # When used as peer config, this represents the endpoint to other peers + # in the same organization. For peers in other organization, see + # gossip.externalEndpoint for more info. + # When used as CLI config, this means the peer's endpoint to interact with + address: 0.0.0.0:7051 + + # Whether the Peer should programmatically determine its address + # This case is useful for docker containers. + # When set to true, will override peer address. + addressAutoDetect: false + + # Settings for the Peer's gateway server. + gateway: + # Whether the gateway is enabled for this Peer. + enabled: true + # endorsementTimeout is the duration the gateway waits for a response + # from other endorsing peers before returning a timeout error to the client. + endorsementTimeout: 30s + # dialTimeout is the duration the gateway waits for a connection + # to other network nodes. + dialTimeout: 2m + + + # Keepalive settings for peer server and clients + keepalive: + # Interval is the duration after which if the server does not see + # any activity from the client it pings the client to see if it's alive + interval: 7200s + # Timeout is the duration the server waits for a response + # from the client after sending a ping before closing the connection + timeout: 20s + # MinInterval is the minimum permitted time between client pings. + # If clients send pings more frequently, the peer server will + # disconnect them + minInterval: 60s + # Client keepalive settings for communicating with other peer nodes + client: + # Interval is the time between pings to peer nodes. This must + # greater than or equal to the minInterval specified by peer + # nodes + interval: 60s + # Timeout is the duration the client waits for a response from + # peer nodes before closing the connection + timeout: 20s + # DeliveryClient keepalive settings for communication with ordering + # nodes. + deliveryClient: + # Interval is the time between pings to ordering nodes. This must + # greater than or equal to the minInterval specified by ordering + # nodes. + interval: 60s + # Timeout is the duration the client waits for a response from + # ordering nodes before closing the connection + timeout: 20s + + + # Gossip related configuration + gossip: + # Bootstrap set to initialize gossip with. + # This is a list of other peers that this peer reaches out to at startup. + # Important: The endpoints here have to be endpoints of peers in the same + # organization, because the peer would refuse connecting to these endpoints + # unless they are in the same organization as the peer. + bootstrap: 127.0.0.1:7051 + + # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. + # Setting both to true would result in the termination of the peer + # since this is undefined state. If the peers are configured with + # useLeaderElection=false, make sure there is at least 1 peer in the + # organization that its orgLeader is set to true. + + # Defines whenever peer will initialize dynamic algorithm for + # "leader" selection, where leader is the peer to establish + # connection with ordering service and use delivery protocol + # to pull ledger blocks from ordering service. + useLeaderElection: false + # Statically defines peer to be an organization "leader", + # where this means that current peer will maintain connection + # with ordering service and disseminate block across peers in + # its own organization. Multiple peers or all peers in an organization + # may be configured as org leaders, so that they all pull + # blocks directly from ordering service. + orgLeader: true + + # Interval for membershipTracker polling + membershipTrackerInterval: 5s + + # Overrides the endpoint that the peer publishes to peers + # in its organization. For peers in foreign organizations + # see 'externalEndpoint' + endpoint: + # Maximum count of blocks stored in memory + maxBlockCountToStore: 10 + # Max time between consecutive message pushes(unit: millisecond) + maxPropagationBurstLatency: 10ms + # Max number of messages stored until a push is triggered to remote peers + maxPropagationBurstSize: 10 + # Number of times a message is pushed to remote peers + propagateIterations: 1 + # Number of peers selected to push messages to + propagatePeerNum: 3 + # Determines frequency of pull phases(unit: second) + # Must be greater than digestWaitTime + responseWaitTime + pullInterval: 4s + # Number of peers to pull from + pullPeerNum: 3 + # Determines frequency of pulling state info messages from peers(unit: second) + requestStateInfoInterval: 4s + # Determines frequency of pushing state info messages to peers(unit: second) + publishStateInfoInterval: 4s + # Maximum time a stateInfo message is kept until expired + stateInfoRetentionInterval: + # Time from startup certificates are included in Alive messages(unit: second) + publishCertPeriod: 10s + # Should we skip verifying block messages or not (currently not in use) + skipBlockVerification: false + # Dial timeout(unit: second) + dialTimeout: 3s + # Connection timeout(unit: second) + connTimeout: 2s + # Buffer size of received messages + recvBuffSize: 20 + # Buffer size of sending messages + sendBuffSize: 200 + # Time to wait before pull engine processes incoming digests (unit: second) + # Should be slightly smaller than requestWaitTime + digestWaitTime: 1s + # Time to wait before pull engine removes incoming nonce (unit: milliseconds) + # Should be slightly bigger than digestWaitTime + requestWaitTime: 1500ms + # Time to wait before pull engine ends pull (unit: second) + responseWaitTime: 2s + # Alive check interval(unit: second) + aliveTimeInterval: 5s + # Alive expiration timeout(unit: second) + aliveExpirationTimeout: 25s + # Reconnect interval(unit: second) + reconnectInterval: 25s + # Max number of attempts to connect to a peer + maxConnectionAttempts: 120 + # Message expiration factor for alive messages + msgExpirationFactor: 20 + # This is an endpoint that is published to peers outside of the organization. + # If this isn't set, the peer will not be known to other organizations. + externalEndpoint: + # Leader election service configuration + election: + # Longest time peer waits for stable membership during leader election startup (unit: second) + startupGracePeriod: 15s + # Interval gossip membership samples to check its stability (unit: second) + membershipSampleInterval: 1s + # Time passes since last declaration message before peer decides to perform leader election (unit: second) + leaderAliveThreshold: 10s + # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) + leaderElectionDuration: 5s + + pvtData: + # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block + # would be attempted to be pulled from peers until the block would be committed without the private data + pullRetryThreshold: 60s + # As private data enters the transient store, it is associated with the peer's ledger's height at that time. + # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, + # and the private data residing inside the transient store that is guaranteed not to be purged. + # Private data is purged from the transient store when blocks with sequences that are multiples + # of transientstoreMaxBlockRetention are committed. + transientstoreMaxBlockRetention: 1000 + # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer + # at private data push at endorsement time. + pushAckTimeout: 3s + # Block to live pulling margin, used as a buffer + # to prevent peer from trying to pull private data + # from peers that is soon to be purged in next N blocks. + # This helps a newly joined peer catch up to current + # blockchain height quicker. + btlPullMargin: 10 + # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to + # pull from the other peers the most recent missing blocks with a maximum batch size limitation. + # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a + # single iteration. + reconcileBatchSize: 10 + # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning + # of the next reconciliation iteration. + reconcileSleepInterval: 1m + # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. + reconciliationEnabled: true + # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid + # transaction's private data from other peers need to be skipped during the commit time and pulled + # only through reconciler. + skipPullingInvalidTransactionsDuringCommit: false + # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. + # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values + # for disseminating private data. + # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to + # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. + implicitCollectionDisseminationPolicy: + # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully + # disseminate private data for its own implicit collection during endorsement. Default value is 0. + requiredPeerCount: 0 + # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to + # disseminate private data for its own implicit collection during endorsement. Default value is 1. + maxPeerCount: 1 + + # Gossip state transfer related configuration + state: + # indicates whenever state transfer is enabled or not + # default value is false, i.e. state transfer is active + # and takes care to sync up missing blocks allowing + # lagging peer to catch up to speed with rest network. + # Keep in mind that when peer.gossip.useLeaderElection is true + # and there are several peers in the organization, + # or peer.gossip.useLeaderElection is false alongside with + # peer.gossip.orgleader being false, the peer's ledger may lag behind + # the rest of the peers and will never catch up due to state transfer + # being disabled. + enabled: false + # checkInterval interval to check whether peer is lagging behind enough to + # request blocks via state transfer from another peer. + checkInterval: 10s + # responseTimeout amount of time to wait for state transfer response from + # other peers + responseTimeout: 3s + # batchSize the number of blocks to request via state transfer from another peer + batchSize: 10 + # blockBufferSize reflects the size of the re-ordering buffer + # which captures blocks and takes care to deliver them in order + # down to the ledger layer. The actual buffer size is bounded between + # 0 and 2*blockBufferSize, each channel maintains its own buffer + blockBufferSize: 20 + # maxRetries maximum number of re-tries to ask + # for single state transfer request + maxRetries: 3 + + # TLS Settings + tls: + # Require server-side TLS + enabled: false + # Require client certificates / mutual TLS for inbound connections. + # Note that clients that are not configured to use a certificate will + # fail to connect to the peer. + clientAuthRequired: false + # X.509 certificate used for TLS server + cert: + file: tls/server.crt + # Private key used for TLS server + key: + file: tls/server.key + # rootcert.file represents the trusted root certificate chain used for verifying certificates + # of other nodes during outbound connections. + # It is not required to be set, but can be used to augment the set of TLS CA certificates + # available from the MSPs of each channel’s configuration. + rootcert: + file: tls/ca.crt + # If mutual TLS is enabled, clientRootCAs.files contains a list of additional root certificates + # used for verifying certificates of client connections. + # It augments the set of TLS CA certificates available from the MSPs of each channel’s configuration. + # Minimally, set your organization's TLS CA root certificate so that the peer can receive join channel requests. + clientRootCAs: + files: + - tls/ca.crt + # Private key used for TLS when making client connections. + # If not set, peer.tls.key.file will be used instead + clientKey: + file: + # X.509 certificate used for TLS when making client connections. + # If not set, peer.tls.cert.file will be used instead + clientCert: + file: + + # Authentication contains configuration parameters related to authenticating + # client messages + authentication: + # the acceptable difference between the current server time and the + # client's time as specified in a client request message + timewindow: 15m + + # Path on the file system where peer will store data (eg ledger). This + # location must be access control protected to prevent unintended + # modification that might corrupt the peer operations. + fileSystemPath: /var/hyperledger/production + + # BCCSP (Blockchain crypto provider): Select which crypto implementation or + # library to use + BCCSP: + Default: SW + # Settings for the SW crypto provider (i.e. when DEFAULT: SW) + SW: + # TODO: The default Hash and Security level needs refactoring to be + # fully configurable. Changing these defaults requires coordination + # SHA2 is hardcoded in several places, not only BCCSP + Hash: SHA2 + Security: 256 + # Location of Key Store + FileKeyStore: + # If "", defaults to 'mspConfigPath'/keystore + KeyStore: + # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) + PKCS11: + # Location of the PKCS11 module library + Library: + # Token Label + Label: + # User PIN + Pin: + Hash: + Security: + + # Path on the file system where peer will find MSP local configurations + mspConfigPath: mspreally + # Identifier of the local MSP + # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- + # Deployers need to change the value of the localMspId string. + # In particular, the name of the local MSP ID of a peer needs + # to match the name of one of the MSPs in each of the channel + # that this peer is a member of. Otherwise this peer's messages + # will not be identified as valid by other nodes. + localMspId: SampleOrg + + # CLI common client config options + client: + # connection timeout + connTimeout: 3s + + # Delivery service related config + deliveryclient: + # Enables this peer to disseminate blocks it pulled from the ordering service + # via gossip. + # Note that 'gossip.state.enabled' controls point to point block replication + # of blocks committed in the past. + blockGossipEnabled: true + # It sets the total time the delivery service may spend in reconnection + # attempts until its retry logic gives up and returns an error + reconnectTotalTimeThreshold: 3600s + + # It sets the delivery service <-> ordering service node connection timeout + connTimeout: 3s + + # It sets the delivery service maximal delay between consecutive retries + reConnectBackoffThreshold: 3600s + + # A list of orderer endpoint addresses which should be overridden + # when found in channel configurations. + addressOverrides: + # - from: + # to: + # caCertsFile: + # - from: + # to: + # caCertsFile: + + # Type for the local MSP - by default it's of type bccsp + localMspType: bccsp + + # Used with Go profiling tools only in none production environment. In + # production, it should be disabled (eg enabled: false) + profile: + enabled: false + listenAddress: 0.0.0.0:6060 + + # Handlers defines custom handlers that can filter and mutate + # objects passing within the peer, such as: + # Auth filter - reject or forward proposals from clients + # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation + # Valid handler definition contains: + # - A name which is a factory method name defined in + # core/handlers/library/library.go for statically compiled handlers + # - library path to shared object binary for pluggable filters + # Auth filters and decorators are chained and executed in the order that + # they are defined. For example: + # authFilters: + # - + # name: FilterOne + # library: /opt/lib/filter.so + # - + # name: FilterTwo + # decorators: + # - + # name: DecoratorOne + # - + # name: DecoratorTwo + # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so + handlers: + authFilters: + - + name: DefaultAuth + - + name: ExpirationCheck # This filter checks identity x509 certificate expiration + decorators: + - + name: DefaultDecorator + endorsers: + escc: + name: DefaultEndorsement + library: + validators: + vscc: + name: DefaultValidation + library: + + # library: /etc/hyperledger/fabric/plugin/escc.so + # Number of goroutines that will execute transaction validation in parallel. + # By default, the peer chooses the number of CPUs on the machine. Set this + # variable to override that choice. + # NOTE: overriding this value might negatively influence the performance of + # the peer so please change this value only if you know what you're doing + validatorPoolSize: + + # The discovery service is used by clients to query information about peers, + # such as - which peers have joined a certain channel, what is the latest + # channel config, and most importantly - given a chaincode and a channel, + # what possible sets of peers satisfy the endorsement policy. + discovery: + enabled: true + # Whether the authentication cache is enabled or not. + authCacheEnabled: true + # The maximum size of the cache, after which a purge takes place + authCacheMaxSize: 1000 + # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation + authCachePurgeRetentionRatio: 0.75 + # Whether to allow non-admins to perform non channel scoped queries. + # When this is false, it means that only peer admins can perform non channel scoped queries. + orgMembersAllowedAccess: false + + # Limits is used to configure some internal resource limits. + limits: + # Concurrency limits the number of concurrently running requests to a service on each peer. + # Currently this option is only applied to endorser service and deliver service. + # When the property is missing or the value is 0, the concurrency limit is disabled for the service. + concurrency: + # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, + # including both user chaincodes and system chaincodes. + endorserService: 2500 + # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. + deliverService: 2500 + + # Since all nodes should be consistent it is recommended to keep + # the default value of 100MB for MaxRecvMsgSize & MaxSendMsgSize + # Max message size in bytes GRPC server and client can receive + maxRecvMsgSize: 104857600 + # Max message size in bytes GRPC server and client can send + maxSendMsgSize: 104857600 + +############################################################################### +# +# VM section +# +############################################################################### +#vm: + + # Endpoint of the vm management system. For docker can be one of the following in general + # unix:///var/run/docker.sock + # http://localhost:2375 + # https://localhost:2376 + # If you utilize external chaincode builders and don't need the default Docker chaincode builder, + # the endpoint should be unconfigured so that the peer's Docker health checker doesn't get registered. +# endpoint: unix:///var/run/docker.sock + + # settings for docker vms + # docker: + # tls: + # enabled: false + # ca: + # file: docker/ca.crt + # cert: + # file: docker/tls.crt + # key: + # file: docker/tls.key + + # Enables/disables the standard out/err from chaincode containers for + # debugging purposes + # attachStdout: false + + # Parameters on creating docker container. + # Container may be efficiently created using ipam & dns-server for cluster + # NetworkMode - sets the networking mode for the container. Supported + # standard values are: `host`(default),`bridge`,`ipvlan`,`none`. + # Dns - a list of DNS servers for the container to use. + # Note: `Privileged` `Binds` `Links` and `PortBindings` properties of + # Docker Host Config are not supported and will not be used if set. + # LogConfig - sets the logging driver (Type) and related options + # (Config) for Docker. For more info, + # https://docs.docker.com/engine/admin/logging/overview/ + # Note: Set LogConfig using Environment Variables is not supported. + # hostConfig: + # NetworkMode: host + # Dns: + # # - 192.168.0.1 + # LogConfig: + # Type: json-file + # Config: + # max-size: "50m" + # max-file: "5" + # Memory: 2147483648 + +############################################################################### +# +# Chaincode section +# +############################################################################### +chaincode: + + # The id is used by the Chaincode stub to register the executing Chaincode + # ID with the Peer and is generally supplied through ENV variables + # the `path` form of ID is provided when installing the chaincode. + # The `name` is used for all other requests and can be any string. + id: + path: + name: + + # Generic builder environment, suitable for most chaincode types + builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) + + # Enables/disables force pulling of the base docker images (listed below) + # during user chaincode instantiation. + # Useful when using moving image tags (such as :latest) + pull: false + + golang: + # golang will never need more than baseos + runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) + + # whether or not golang chaincode should be linked dynamically + dynamicLink: false + + java: + # This is an image based on java:openjdk-8 with addition compiler + # tools added for java shim layer packaging. + # This image is packed with shim layer libraries that are necessary + # for Java chaincode runtime. + runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) + + node: + # This is an image based on node:$(NODE_VER)-alpine + runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) + + # List of directories to treat as external builders and launchers for + # chaincode. The external builder detection processing will iterate over the + # builders in the order specified below. + # If you don't need to fallback to the default Docker builder, also unconfigure vm.endpoint above. + # To override this property via env variable use CORE_CHAINCODE_EXTERNALBUILDERS: [{name: x, path: dir1}, {name: y, path: dir2}] + externalBuilders: + - name: ccaas_builder + path: /opt/hyperledger/ccaas_builder + propagateEnvironment: + - CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG + + + # The maximum duration to wait for the chaincode build and install process + # to complete. + installTimeout: 300s + + # Timeout duration for starting up a container and waiting for Register + # to come through. + startuptimeout: 300s + + # Timeout duration for Invoke and Init calls to prevent runaway. + # This timeout is used by all chaincodes in all the channels, including + # system chaincodes. + # Note that during Invoke, if the image is not available (e.g. being + # cleaned up when in development environment), the peer will automatically + # build the image, which might take more time. In production environment, + # the chaincode image is unlikely to be deleted, so the timeout could be + # reduced accordingly. + executetimeout: 30s + + # There are 2 modes: "dev" and "net". + # In dev mode, user runs the chaincode after starting peer from + # command line on local machine. + # In net mode, peer will run chaincode in a docker container. + mode: net + + # keepalive in seconds. In situations where the communication goes through a + # proxy that does not support keep-alive, this parameter will maintain connection + # between peer and chaincode. + # A value <= 0 turns keepalive off + keepalive: 0 + + # enabled system chaincodes + system: + _lifecycle: enable + cscc: enable + lscc: enable + qscc: enable + + # Logging section for the chaincode container + logging: + # Default level for all loggers within the chaincode container + level: info + # Override default level for the 'shim' logger + shim: warning + # Format for the chaincode container logs + format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' + +############################################################################### +# +# Ledger section - ledger configuration encompasses both the blockchain +# and the state +# +############################################################################### +ledger: + + blockchain: + + state: + # stateDatabase - options are "goleveldb", "CouchDB" + # goleveldb - default state database stored in goleveldb. + # CouchDB - store state database in CouchDB + stateDatabase: goleveldb + # Limit on the number of records to return per query + totalQueryLimit: 100000 + couchDBConfig: + # It is recommended to run CouchDB on the same server as the peer, and + # not map the CouchDB container port to a server port in docker-compose. + # Otherwise proper security must be provided on the connection between + # CouchDB client (on the peer) and server. + couchDBAddress: 127.0.0.1:5984 + # This username must have read and write authority on CouchDB + username: + # The password is recommended to pass as an environment variable + # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). + # If it is stored here, the file must be access control protected + # to prevent unintended users from discovering the password. + password: + # Number of retries for CouchDB errors + maxRetries: 3 + # Number of retries for CouchDB errors during peer startup. + # The delay between retries doubles for each attempt. + # Default of 10 retries results in 11 attempts over 2 minutes. + maxRetriesOnStartup: 10 + # CouchDB request timeout (unit: duration, e.g. 20s) + requestTimeout: 35s + # Limit on the number of records per each CouchDB query + # Note that chaincode queries are only bound by totalQueryLimit. + # Internally the chaincode may execute multiple CouchDB queries, + # each of size internalQueryLimit. + internalQueryLimit: 1000 + # Limit on the number of records per CouchDB bulk update batch + maxBatchUpdateSize: 1000 + # Create the _global_changes system database + # This is optional. Creating the global changes database will require + # additional system resources to track changes and maintain the database + createGlobalChangesDB: false + # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state + # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple + # of 32 MB, the peer would round the size to the next multiple of 32 MB. + # To disable the cache, 0 MB needs to be assigned to the cacheSize. + cacheSize: 64 + + history: + # enableHistoryDatabase - options are true or false + # Indicates if the history of key updates should be stored. + # All history 'index' will be stored in goleveldb, regardless if using + # CouchDB or alternate database for the state. + enableHistoryDatabase: true + + pvtdataStore: + # the maximum db batch size for converting + # the ineligible missing data entries to eligible missing data entries + collElgProcMaxDbBatchSize: 5000 + # the minimum duration (in milliseconds) between writing + # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries + collElgProcDbBatchesInterval: 1000 + # The missing data entries are classified into two categories: + # (1) prioritized + # (2) deprioritized + # Initially, all missing data are in the prioritized list. When the + # reconciler is unable to fetch the missing data from other peers, + # the unreconciled missing data would be moved to the deprioritized list. + # The reconciler would retry deprioritized missing data after every + # deprioritizedDataReconcilerInterval (unit: minutes). Note that the + # interval needs to be greater than the reconcileSleepInterval + deprioritizedDataReconcilerInterval: 60m + + snapshots: + # Path on the file system where peer will store ledger snapshots + rootDir: /var/hyperledger/production/snapshots + +############################################################################### +# +# Operations section +# +############################################################################### +operations: + # host and port for the operations server + listenAddress: 127.0.0.1:9443 + + # 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: + + # most operations service endpoints require client authentication when TLS + # is enabled. clientAuthRequired requires client certificate authentication + # at the TLS layer to access all resources. + clientAuthRequired: false + + # paths to PEM encoded ca certificates to trust for client authentication + clientRootCAs: + files: [] + +############################################################################### +# +# Metrics section +# +############################################################################### +metrics: + # metrics provider is one of 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 pushed + # to statsd; timings are pushed immediately + writeInterval: 10s + + # prefix is prepended to all emitted statsd metrics + prefix: diff --git a/test-network/compose/podman/podman-compose-ca.yaml b/test-network/compose/podman/podman-compose-ca.yaml new file mode 100644 index 00000000..6ab883d4 --- /dev/null +++ b/test-network/compose/podman/podman-compose-ca.yaml @@ -0,0 +1,6 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' diff --git a/test-network/compose/podman/podman-compose-couch.yaml b/test-network/compose/podman/podman-compose-couch.yaml new file mode 100644 index 00000000..6ab883d4 --- /dev/null +++ b/test-network/compose/podman/podman-compose-couch.yaml @@ -0,0 +1,6 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' diff --git a/test-network/compose/podman/podman-compose-test-net.yaml b/test-network/compose/podman/podman-compose-test-net.yaml new file mode 100644 index 00000000..8afe4149 --- /dev/null +++ b/test-network/compose/podman/podman-compose-test-net.yaml @@ -0,0 +1,19 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +version: '3.7' + +services: + peer0.org1.example.com: + volumes: + - ./podman/peercfg:/etc/hyperledger/peercfg + + peer0.org2.example.com: + volumes: + - ./podman/peercfg:/etc/hyperledger/peercfg + + cli: + volumes: + - ./podman/peercfg:/etc/hyperledger/peercfg diff --git a/test-network/network.sh b/test-network/network.sh index c6c3fdef..73046e73 100755 --- a/test-network/network.sh +++ b/test-network/network.sh @@ -12,18 +12,32 @@ # # prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries # this may be commented out to resolve installed version of tools if desired -export PATH=${PWD}/../bin:$PATH +# +# However using PWD in the path has the side effect that location that +# this script is run from is critical. To ease this, get the directory +# this script is actually in and infer location from there. (putting first) + +ROOTDIR=$(cd "$(dirname "$0")" && pwd) +export PATH=${ROOTDIR}/../bin:${PWD}/../bin:$PATH export FABRIC_CFG_PATH=${PWD}/configtx export VERBOSE=false +# push to the required directory & set a trap to go back if needed +pushd ${ROOTDIR} > /dev/null +trap "popd > /dev/null" EXIT + . scripts/utils.sh +: ${CONTAINER_CLI:="docker"} +: ${CONTAINER_CLI_COMPOSE:="${CONTAINER_CLI}-compose"} +infoln "Using ${CONTAINER_CLI} and ${CONTAINER_CLI_COMPOSE}" + # Obtain CONTAINER_IDS and remove them # This function is called when you bring a network down function clearContainers() { infoln "Removing remaining containers" - docker rm -f $(docker ps -aq --filter label=service=hyperledger-fabric) 2>/dev/null || true - docker rm -f $(docker ps -aq --filter name='dev-peer*') 2>/dev/null || true + ${CONTAINER_CLI} rm -f $(${CONTAINER_CLI} ps -aq --filter label=service=hyperledger-fabric) 2>/dev/null || true + ${CONTAINER_CLI} rm -f $(${CONTAINER_CLI} ps -aq --filter name='dev-peer*') 2>/dev/null || true } # Delete any images that were generated as a part of this setup @@ -31,7 +45,7 @@ function clearContainers() { # This function is called when you bring the network down function removeUnwantedImages() { infoln "Removing generated chaincode docker images" - docker image rm -f $(docker images -aq --filter reference='dev-peer*') 2>/dev/null || true + ${CONTAINER_CLI} image rm -f $(${CONTAINER_CLI} images -aq --filter reference='dev-peer*') 2>/dev/null || true } # Versions of fabric known not to work with the test network @@ -54,7 +68,7 @@ function checkPrereqs() { # use the fabric tools container to see if the samples and binaries match your # docker images LOCAL_VERSION=$(peer version | sed -ne 's/^ Version: //p') - DOCKER_IMAGE_VERSION=$(docker run --rm hyperledger/fabric-tools:latest peer version | sed -ne 's/^ Version: //p') + DOCKER_IMAGE_VERSION=$(${CONTAINER_CLI} run --rm hyperledger/fabric-tools:latest peer version | sed -ne 's/^ Version: //p') infoln "LOCAL_VERSION=$LOCAL_VERSION" infoln "DOCKER_IMAGE_VERSION=$DOCKER_IMAGE_VERSION" @@ -170,7 +184,7 @@ function createOrgs() { # Create crypto material using Fabric CA if [ "$CRYPTO" == "Certificate Authorities" ]; then infoln "Generating certificates using Fabric CA" - docker-compose -f $COMPOSE_FILE_CA up -d 2>&1 + ${CONTAINER_CLI_COMPOSE} -f compose/$COMPOSE_FILE_CA -f compose/$CONTAINER_CLI/${CONTAINER_CLI}-$COMPOSE_FILE_CA up -d 2>&1 . organizations/fabric-ca/registerEnroll.sh @@ -230,20 +244,21 @@ function createOrgs() { # Bring up the peer and orderer nodes using docker compose. function networkUp() { checkPrereqs + # generate artifacts if they don't exist if [ ! -d "organizations/peerOrganizations" ]; then createOrgs fi - COMPOSE_FILES="-f ${COMPOSE_FILE_BASE}" + COMPOSE_FILES="-f compose/${COMPOSE_FILE_BASE} -f compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_BASE}" if [ "${DATABASE}" == "couchdb" ]; then - COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}" + COMPOSE_FILES="${COMPOSE_FILES} -f compose/${COMPOSE_FILE_COUCH} -f compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_COUCH}" fi - DOCKER_SOCK="${DOCKER_SOCK}" docker-compose ${COMPOSE_FILES} up -d 2>&1 + DOCKER_SOCK="${DOCKER_SOCK}" ${CONTAINER_CLI_COMPOSE} ${COMPOSE_FILES} up -d 2>&1 - docker ps -a + $CONTAINER_CLI ps -a if [ $? -ne 0 ]; then fatalln "Unable to start network" fi @@ -253,8 +268,24 @@ function networkUp() { # and then update the anchor peers for each organization function createChannel() { # Bring up the network if it is not already up. + bringUpNetwork="false" - if [ ! -d "organizations/peerOrganizations" ]; then + if ! $CONTAINER_CLI info > /dev/null 2>&1 ; then + fatalln "$CONTAINER_CLI network is required to be running to create a channel" + fi + + # check if all containers are present + CONTAINERS=($($CONTAINER_CLI ps | grep hyperledger/ | awk '{print $2}')) + len=$(echo ${#CONTAINERS[@]}) + + if [[ $len -ge 4 ]] && [[ ! -d "organizations/peerOrganizations" ]]; then + echo "Bringing network down to sync certs with containers" + networkDown + fi + + [[ $len -lt 4 ]] || [[ ! -d "organizations/peerOrganizations" ]] && bringUpNetwork="true" || echo "Network Running Already" + + if [ $bringUpNetwork == "true" ]; then infoln "Bringing up network" networkUp fi @@ -285,28 +316,46 @@ function deployCCAAS() { # Tear down running network function networkDown() { + + COMPOSE_BASE_FILES="-f compose/${COMPOSE_FILE_BASE} -f compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_BASE}" + COMPOSE_COUCH_FILES="-f compose/${COMPOSE_FILE_COUCH} -f compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_COUCH}" + COMPOSE_CA_FILES="-f compose/${COMPOSE_FILE_CA} -f compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_CA}" + COMPOSE_FILES="${COMPOSE_BASE_FILES} ${COMPOSE_COUCH_FILES} ${COMPOSE_CA_FILES}" + # stop org3 containers also in addition to org1 and org2, in case we were running sample to add org3 - DOCKER_SOCK=$DOCKER_SOCK docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans - docker-compose -f $COMPOSE_FILE_COUCH_ORG3 -f $COMPOSE_FILE_ORG3 down --volumes --remove-orphans + COMPOSE_ORG3_BASE_FILES="-f addOrg3/compose/${COMPOSE_FILE_ORG3_BASE} -f addOrg3/compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_ORG3_BASE}" + COMPOSE_ORG3_COUCH_FILES="-f addOrg3/compose/${COMPOSE_FILE_ORG3_COUCH} -f addOrg3/compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_ORG3_COUCH}" + COMPOSE_ORG3_CA_FILES="-f addOrg3/compose/${COMPOSE_FILE_ORG3_CA} -f addOrg3/compose/${CONTAINER_CLI}/${CONTAINER_CLI}-${COMPOSE_FILE_ORG3_CA}" + COMPOSE_ORG3_FILES="${COMPOSE_ORG3_BASE_FILES} ${COMPOSE_ORG3_COUCH_FILES} ${COMPOSE_ORG3_CA_FILES}" + + if [ "${CONTAINER_CLI}" == "docker" ]; then + DOCKER_SOCK=$DOCKER_SOCK ${CONTAINER_CLI_COMPOSE} ${COMPOSE_FILES} ${COMPOSE_ORG3_FILES} down --volumes --remove-orphans + elif [ "${CONTAINER_CLI}" == "podman" ]; then + ${CONTAINER_CLI_COMPOSE} ${COMPOSE_FILES} ${COMPOSE_ORG3_FILES} down --volumes + else + fatalln "Container CLI ${CONTAINER_CLI} not supported" + fi + + # Don't remove the generated artifacts -- note, the ledgers are always removed if [ "$MODE" != "restart" ]; then # Bring down the network, deleting the volumes - docker volume rm docker_orderer.example.com docker_peer0.org1.example.com docker_peer0.org2.example.com + ${CONTAINER_CLI} volume rm docker_orderer.example.com docker_peer0.org1.example.com docker_peer0.org2.example.com #Cleanup the chaincode containers clearContainers #Cleanup images removeUnwantedImages # - docker kill $(docker ps -q --filter name=ccaas) || true + ${CONTAINER_CLI} kill $(${CONTAINER_CLI} ps -q --filter name=ccaas) || true # remove orderer block and other channel configuration transactions and certs - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf system-genesis-block/*.block organizations/peerOrganizations organizations/ordererOrganizations' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf system-genesis-block/*.block organizations/peerOrganizations organizations/ordererOrganizations' ## remove fabric ca artifacts - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org1/msp organizations/fabric-ca/org1/tls-cert.pem organizations/fabric-ca/org1/ca-cert.pem organizations/fabric-ca/org1/IssuerPublicKey organizations/fabric-ca/org1/IssuerRevocationPublicKey organizations/fabric-ca/org1/fabric-ca-server.db' - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org2/msp organizations/fabric-ca/org2/tls-cert.pem organizations/fabric-ca/org2/ca-cert.pem organizations/fabric-ca/org2/IssuerPublicKey organizations/fabric-ca/org2/IssuerRevocationPublicKey organizations/fabric-ca/org2/fabric-ca-server.db' - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/ordererOrg/msp organizations/fabric-ca/ordererOrg/tls-cert.pem organizations/fabric-ca/ordererOrg/ca-cert.pem organizations/fabric-ca/ordererOrg/IssuerPublicKey organizations/fabric-ca/ordererOrg/IssuerRevocationPublicKey organizations/fabric-ca/ordererOrg/fabric-ca-server.db' - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf addOrg3/fabric-ca/org3/msp addOrg3/fabric-ca/org3/tls-cert.pem addOrg3/fabric-ca/org3/ca-cert.pem addOrg3/fabric-ca/org3/IssuerPublicKey addOrg3/fabric-ca/org3/IssuerRevocationPublicKey addOrg3/fabric-ca/org3/fabric-ca-server.db' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org1/msp organizations/fabric-ca/org1/tls-cert.pem organizations/fabric-ca/org1/ca-cert.pem organizations/fabric-ca/org1/IssuerPublicKey organizations/fabric-ca/org1/IssuerRevocationPublicKey organizations/fabric-ca/org1/fabric-ca-server.db' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org2/msp organizations/fabric-ca/org2/tls-cert.pem organizations/fabric-ca/org2/ca-cert.pem organizations/fabric-ca/org2/IssuerPublicKey organizations/fabric-ca/org2/IssuerRevocationPublicKey organizations/fabric-ca/org2/fabric-ca-server.db' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/ordererOrg/msp organizations/fabric-ca/ordererOrg/tls-cert.pem organizations/fabric-ca/ordererOrg/ca-cert.pem organizations/fabric-ca/ordererOrg/IssuerPublicKey organizations/fabric-ca/ordererOrg/IssuerRevocationPublicKey organizations/fabric-ca/ordererOrg/fabric-ca-server.db' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf addOrg3/fabric-ca/org3/msp addOrg3/fabric-ca/org3/tls-cert.pem addOrg3/fabric-ca/org3/ca-cert.pem addOrg3/fabric-ca/org3/IssuerPublicKey addOrg3/fabric-ca/org3/IssuerRevocationPublicKey addOrg3/fabric-ca/org3/fabric-ca-server.db' # remove channel and script artifacts - docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf channel-artifacts log.txt *.tar.gz' + ${CONTAINER_CLI} run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf channel-artifacts log.txt *.tar.gz' fi } @@ -330,15 +379,17 @@ CC_COLL_CONFIG="NA" # chaincode init function defaults to "NA" CC_INIT_FCN="NA" # use this as the default docker-compose yaml definition -COMPOSE_FILE_BASE=docker/docker-compose-test-net.yaml +COMPOSE_FILE_BASE=compose-test-net.yaml # docker-compose.yaml file if you are using couchdb -COMPOSE_FILE_COUCH=docker/docker-compose-couch.yaml +COMPOSE_FILE_COUCH=compose-couch.yaml # certificate authorities compose file -COMPOSE_FILE_CA=docker/docker-compose-ca.yaml -# use this as the docker compose couch file for org3 -COMPOSE_FILE_COUCH_ORG3=addOrg3/docker/docker-compose-couch-org3.yaml +COMPOSE_FILE_CA=compose-ca.yaml # use this as the default docker-compose yaml definition for org3 -COMPOSE_FILE_ORG3=addOrg3/docker/docker-compose-org3.yaml +COMPOSE_FILE_ORG3_BASE=compose-org3.yaml +# use this as the docker compose couch file for org3 +COMPOSE_FILE_ORG3_COUCH=compose-couch-org3.yaml +# certificate authorities compose file +COMPOSE_FILE_ORG3_CA=compose-ca-org3.yaml # # chaincode language defaults to "NA" CC_SRC_LANGUAGE="NA" @@ -483,4 +534,3 @@ else printHelp exit 1 fi - diff --git a/test-network/organizations/fabric-ca/registerEnroll.sh b/test-network/organizations/fabric-ca/registerEnroll.sh index d7e06ad5..181c270e 100755 --- a/test-network/organizations/fabric-ca/registerEnroll.sh +++ b/test-network/organizations/fabric-ca/registerEnroll.sh @@ -7,7 +7,7 @@ function createOrg1() { export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/ set -x - fabric-ca-client enroll -u https://admin:adminpw@localhost:7054 --caname ca-org1 --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client enroll -u https://admin:adminpw@localhost:7054 --caname ca-org1 --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null echo 'NodeOUs: @@ -25,56 +25,62 @@ function createOrg1() { Certificate: cacerts/localhost-7054-ca-org1.pem OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" + # Since the CA serves as both the organization CA and TLS CA, copy the org's root cert that was generated by CA startup into the org level ca and tlsca directories + + # Copy org1's CA cert to org1's /msp/tlscacerts directory (for use in the channel MSP definition) + mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts" + cp "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/ca.crt" + + # Copy org1's CA cert to org1's /tlsca directory (for use by clients) + mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca" + cp "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem" + + # Copy org1's CA cert to org1's /ca directory (for use by clients) + mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/ca" + cp "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem" + infoln "Registering peer0" set -x - fabric-ca-client register --caname ca-org1 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client register --caname ca-org1 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null infoln "Registering user" set -x - fabric-ca-client register --caname ca-org1 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client register --caname ca-org1 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null infoln "Registering the org admin" set -x - fabric-ca-client register --caname ca-org1 --id.name org1admin --id.secret org1adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client register --caname ca-org1 --id.name org1admin --id.secret org1adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null infoln "Generating the peer0 msp" set -x - fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp" --csr.hosts peer0.org1.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp" --csr.hosts peer0.org1.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/config.yaml" infoln "Generating the peer0-tls certificates" set -x - fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org1.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org1.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null + # Copy the tls CA cert, server cert, server keystore to well known file names in the peer's tls directory that are referenced by peer startup config cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/signcerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt" cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/keystore/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key" - mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts" - cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/ca.crt" - - mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca" - cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem" - - mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/ca" - cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/cacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem" - infoln "Generating the user msp" set -x - fabric-ca-client enroll -u https://user1:user1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client enroll -u https://user1:user1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/config.yaml" infoln "Generating the org admin msp" set -x - fabric-ca-client enroll -u https://org1admin:org1adminpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" + fabric-ca-client enroll -u https://org1admin:org1adminpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml" @@ -87,7 +93,7 @@ function createOrg2() { export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ set -x - fabric-ca-client enroll -u https://admin:adminpw@localhost:8054 --caname ca-org2 --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client enroll -u https://admin:adminpw@localhost:8054 --caname ca-org2 --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null echo 'NodeOUs: @@ -105,56 +111,62 @@ function createOrg2() { Certificate: cacerts/localhost-8054-ca-org2.pem OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" + # Since the CA serves as both the organization CA and TLS CA, copy the org's root cert that was generated by CA startup into the org level ca and tlsca directories + + # Copy org2's CA cert to org2's /msp/tlscacerts directory (for use in the channel MSP definition) + mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts" + cp "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/ca.crt" + + # Copy org2's CA cert to org2's /tlsca directory (for use by clients) + mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca" + cp "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem" + + # Copy org2's CA cert to org2's /ca directory (for use by clients) + mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/ca" + cp "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" "${PWD}/organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem" + infoln "Registering peer0" set -x - fabric-ca-client register --caname ca-org2 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client register --caname ca-org2 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null infoln "Registering user" set -x - fabric-ca-client register --caname ca-org2 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client register --caname ca-org2 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null infoln "Registering the org admin" set -x - fabric-ca-client register --caname ca-org2 --id.name org2admin --id.secret org2adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client register --caname ca-org2 --id.name org2admin --id.secret org2adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null infoln "Generating the peer0 msp" set -x - fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp" --csr.hosts peer0.org2.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp" --csr.hosts peer0.org2.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/config.yaml" infoln "Generating the peer0-tls certificates" set -x - fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org2.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org2.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null + # Copy the tls CA cert, server cert, server keystore to well known file names in the peer's tls directory that are referenced by peer startup config cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/signcerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt" cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/keystore/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key" - mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts" - cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/ca.crt" - - mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca" - cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem" - - mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/ca" - cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/cacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem" - infoln "Generating the user msp" set -x - fabric-ca-client enroll -u https://user1:user1pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client enroll -u https://user1:user1pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/config.yaml" infoln "Generating the org admin msp" set -x - fabric-ca-client enroll -u https://org2admin:org2adminpw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" + fabric-ca-client enroll -u https://org2admin:org2adminpw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/config.yaml" @@ -167,7 +179,7 @@ function createOrderer() { export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/ordererOrganizations/example.com set -x - fabric-ca-client enroll -u https://admin:adminpw@localhost:9054 --caname ca-orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client enroll -u https://admin:adminpw@localhost:9054 --caname ca-orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null echo 'NodeOUs: @@ -185,41 +197,50 @@ function createOrderer() { Certificate: cacerts/localhost-9054-ca-orderer.pem OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml" + # Since the CA serves as both the organization CA and TLS CA, copy the org's root cert that was generated by CA startup into the org level ca and tlsca directories + + # Copy orderer org's CA cert to orderer org's /msp/tlscacerts directory (for use in the channel MSP definition) + mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts" + cp "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem" + + # Copy orderer org's CA cert to orderer org's /tlsca directory (for use by clients) + mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/tlsca" + cp "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" "${PWD}/organizations/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem" + infoln "Registering orderer" set -x - fabric-ca-client register --caname ca-orderer --id.name orderer --id.secret ordererpw --id.type orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client register --caname ca-orderer --id.name orderer --id.secret ordererpw --id.type orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null infoln "Registering the orderer admin" set -x - fabric-ca-client register --caname ca-orderer --id.name ordererAdmin --id.secret ordererAdminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client register --caname ca-orderer --id.name ordererAdmin --id.secret ordererAdminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null infoln "Generating the orderer msp" set -x - fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp" --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp" --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml" "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/config.yaml" infoln "Generating the orderer-tls certificates" set -x - fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls" --enrollment.profile tls --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls" --enrollment.profile tls --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null + # Copy the tls CA cert, server cert, server keystore to well known file names in the orderer's tls directory that are referenced by orderer startup config cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt" cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/signcerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt" cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/keystore/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key" + # Copy orderer org's CA cert to orderer's /msp/tlscacerts directory (for use in the orderer MSP definition) mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts" cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" - mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts" - cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem" - infoln "Generating the admin msp" set -x - fabric-ca-client enroll -u https://ordererAdmin:ordererAdminpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem" + fabric-ca-client enroll -u https://ordererAdmin:ordererAdminpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/ca-cert.pem" { set +x; } 2>/dev/null cp "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml" "${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp/config.yaml" diff --git a/test-network/prometheus-grafana/README.md b/test-network/prometheus-grafana/README.md index 64abae9e..6da075d5 100644 --- a/test-network/prometheus-grafana/README.md +++ b/test-network/prometheus-grafana/README.md @@ -1,49 +1,52 @@ # Description -This sample provides an environment to display and capture metrics from the test-network in real time. It consists of a docker-compose file that starts a prometheus and grafana server setup configured to collect and display metrics for the test network. +This sample provides an environment to display and capture metrics from the test-network in real time. It consists of a docker-compose file that starts a Prometheus and Grafana server setup configured to collect and display metrics for the test network. -# Requirements +## Requirements -This sample has been tested and is recomended to be used on **linux** in order to fully benefit from its capabilities, however it can be deployed and works on MacOS-intel machine as well (some modification to the cadvisor docker image and related queries are required to show docker containers metrics). +This sample has been tested and is recommended to be used on **linux** in order to fully benefit from its capabilities, however it can be deployed and works on MacOS-intel machine as well (some modification to the cadvisor docker image and related queries are required to show docker containers metrics). You will need to have installed **docker-compose with version 1.29 or above** (note that this is higher than the v1.14 requirement requested for the test-network). -# How to use +## How to use 1. Go to the test-network directory and run bring up the test-network **./network.sh up createChannel** 2. Bring up the Prometheus/Grafana network in the test-network/prometheus-grafana directory and run **docker-compose up -d** 3. Log in: type “localhost:3000” on your web browser -> username=“admin”, password=“admin” -> set a new password -4. Browse dashboard and analyse results +4. Browse dashboard and analyse results - The default dashboard "HLF Performances Review" can be found and displayed by hovering over the dashboard menu and clicking on the browse button. ![picture alt]("https://user-images.githubusercontent.com/86831094/149115445-5e5f6d95-ecc3-4b46-aadb-5c01148770b3.png "Title is optional") Once opened the dashboard, to display the collected metrics and data, adjust the timeframe on the top right to focus on the latest timespan when the network was up. -5. Deploy a chaincode (i.e. "./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"), start using the test-network and use the grafana dashboard to analyse and assess your network performances. -Extras: add new queries, modify dashboard & add relevant changes to main repo --> extract json and add it to "grafana/dashboards/hlf-performances.json". +5. Deploy a chaincode (i.e. "./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"), start using the test-network and use the Grafana dashboard to analyse and assess your network performances. +Extras: add new queries, modify dashboard & add relevant changes to main repo --> extract json and add it to "Grafana/dashboards/hlf-performances.json". Metrics can also be displayed directly from Prometheus by going to "localhost:9090". -# Docker Compose +## Docker Compose Brings up - - a prometheus sever (port 9090) -> pulls metrics from peers, orderer, system(node exporter) and containers(cadvisor) - - grafana sever (port 3000) -> collects and display data from prometheus - - node exporter (port 9100) -> exposes systems metrics - - cadvisor (port 8080) -> exposes docker containers metrics -# Prometheus "configuration file" +- a Prometheus server (port 9090) -> pulls metrics from peers, orderer, system(node exporter) and containers(cadvisor) +- Grafana server (port 3000) -> collects and display data from Prometheus +- node exporter (port 9100) -> exposes systems metrics +- cadvisor (port 8080) -> exposes docker containers metrics -**Prometheus.yml** +## Prometheus "configuration file" + +### Prometheus.yml Fabric metrics targets: - - peer0.org1.example.com:9444 - - peer0.org2.example.com:9445 - - orderer.example.com:9443 + +- `peer0.org1.example.com:9444` +- `peer0.org2.example.com:9445` +- `orderer.example.com:9443` System and docker metrics targets: - - cadvisor:8080 - - node-exporter:9100 + +- `cadvisor:8080` +- `node-exporter:9100` Check the state of the connections with targets on http://localhost:9090/targets. -# Sources +## Sources -Prometheus docs: https://prometheus.io/docs/introduction/overview/ -Grafana docs: https://grafana.com/docs/ +[Prometheus docs](https://prometheus.io/docs/introduction/overview/) +[Grafana docs](https://grafana.com/docs/) diff --git a/test-network/prometheus-grafana/docker-compose.yaml b/test-network/prometheus-grafana/docker-compose.yaml index 992fe69c..b166f6ea 100644 --- a/test-network/prometheus-grafana/docker-compose.yaml +++ b/test-network/prometheus-grafana/docker-compose.yaml @@ -6,7 +6,7 @@ volumes: services: prometheus: - image: prom/prometheus + image: prom/prometheus:v2.32.1 container_name: prometheus volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml @@ -20,7 +20,7 @@ services: - "9090:9090" grafana: - image: grafana/grafana + image: grafana/grafana:8.3.4 container_name: grafana user: "104" depends_on: @@ -48,7 +48,7 @@ services: restart: always node-exporter: - image: prom/node-exporter + image: prom/node-exporter:v1.3.1 container_name: node-exporter volumes: - /proc:/host/proc:ro diff --git a/test-network/prometheus-grafana/grafana/provisioning/dashboards/hlf-performances.json b/test-network/prometheus-grafana/grafana/provisioning/dashboards/hlf-performances.json index c9d41fa2..8bee1bd3 100644 --- a/test-network/prometheus-grafana/grafana/provisioning/dashboards/hlf-performances.json +++ b/test-network/prometheus-grafana/grafana/provisioning/dashboards/hlf-performances.json @@ -1,2430 +1,2034 @@ { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "Overview of the most important Fabric, system and Docker container metrics. ", - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 893, - "graphTooltip": 1, - "iteration": 1642423515757, - "links": [], - "liveNow": false, - "panels": [ - { - "aliasColors": { - "{id=\"/\",instance=\"cadvisor:8080\",job=\"prometheus\"}": "#BA43A9" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "editable": true, - "error": false, - "fill": 1, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 6, - "w": 5, - "x": 0, - "y": 0 - }, - "hiddenSeries": false, - "id": 5, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_system_seconds_total[1m]))/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "a", - "refId": "B", - "step": 120 - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_system_seconds_total{name=~\".+\"}[1m]))/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "nur container", - "refId": "F", - "step": 10 - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_system_seconds_total{id=\"/\"}[1m]))/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "nur docker host", - "metric": "", - "refId": "A", - "step": 20 - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(process_cpu_seconds_total[$interval]))/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode) * 100", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "host", - "metric": "", - "refId": "C", - "step": 600 - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "sum(rate(container_cpu_system_seconds_total{name=~\".+\"}[1m])) + sum(rate(container_cpu_system_seconds_total{id=\"/\"}[1m])) + sum(rate(process_cpu_seconds_total[1m]))", - "hide": true, - "intervalFactor": 2, - "legendFormat": "", - "refId": "D", - "step": 120 - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\".+\"}[$interval]))/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode) * 100", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "E" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "CPU Usage", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": false, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:7341", - "format": "percent", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:7342", - "format": "short", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org1.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org1.example.com-fixed-asset" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org2.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org2.example.com-fixed-asset" - } - ] - } - ] - }, - "fill": 3, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 6, - "w": 6, - "x": 5, - "y": 0 - }, - "hiddenSeries": false, - "id": 59, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(container_memory_rss{name=~\".+\"}) by (name)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "A", - "step": 240 - }, - { - "exemplar": true, - "expr": "sum(container_memory_usage_bytes{name=~\".+\"} - container_memory_cache{name=~\".+\"})", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "refId": "B", - "step": 240 - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Total Memory Usage per Container", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1101", - "format": "decbytes", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:1102", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 3, - "x": 11, - "y": 0 - }, - "id": 31, - "links": [], - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.3.1", - "targets": [ - { - "expr": "count(rate(container_last_seen{name=~\".+\"}[$interval]))", - "intervalFactor": 2, - "refId": "A", - "step": 1800 - } - ], - "title": "Containers", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(50, 172, 45, 0.97)", - "value": null - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 0.75 - }, - { - "color": "rgba(245, 54, 54, 0.9)", - "value": 0.9 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 5, - "x": 14, - "y": 0 - }, - "id": 26, - "links": [], - "maxDataPoints": 100, - "options": { - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "8.3.1", - "targets": [ - { - "exemplar": true, - "expr": "min((node_filesystem_size_bytes{fstype=~\"xfs|tmpfs\"} - node_filesystem_free_bytes{fstype=~\"xfs|tmpfs\"} )/ node_filesystem_size_bytes{fstype=~\"xfs|tmpfs\"})", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "refId": "A", - "step": 1800 - } - ], - "title": "Disk space", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(50, 172, 45, 0.97)", - "value": null - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 70 - }, - { - "color": "rgba(245, 54, 54, 0.9)", - "value": 90 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 5, - "x": 19, - "y": 0 - }, - "id": 25, - "links": [], - "maxDataPoints": 100, - "options": { - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "8.3.1", - "targets": [ - { - "exemplar": true, - "expr": "((node_memory_MemTotal_bytes{} - node_memory_MemAvailable_bytes{}) / node_memory_MemTotal_bytes{}) * 100", - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "refId": "A", - "step": 1800 - } - ], - "title": "Memory", - "type": "gauge" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 6 - }, - "id": 47, - "panels": [], - "title": "Docker Containers Metrics", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org1.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org1.example.com-fixed-asset" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org2.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org2.example.com-fixed-asset" - } - ] - } - ] - }, - "fill": 5, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 7 - }, - "hiddenSeries": false, - "id": 1, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\".+\"}[$interval])) by (name)/ count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode) * 100", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "metric": "", - "refId": "F", - "step": 240 - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Total CPU Usage per Container", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:2189", - "format": "percent", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:2190", - "format": "short", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org1.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org1.example.com-fixed-asset" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org2.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org2.example.com-fixed-asset" - } - ] - } - ] - }, - "fill": 3, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 7 - }, - "hiddenSeries": false, - "id": 10, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(container_memory_rss{name=~\".+\"}) by (name)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "A", - "step": 240 - }, - { - "exemplar": true, - "expr": "sum(container_memory_usage_bytes{name=~\".+\"} - container_memory_cache{name=~\".+\"}) by (name)", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "B", - "step": 240 - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Total Memory Usage per Container", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1101", - "format": "decbytes", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:1102", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "displayMode": "auto" - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 10000000 - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 25000000 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 17 - }, - "id": 37, - "links": [], - "options": { - "footer": { - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "8.3.1", - "targets": [ - { - "expr": "sum(container_spec_memory_limit_bytes{name=~\".+\"} - container_memory_usage_bytes{name=~\".+\"}) by (name) ", - "hide": true, - "intervalFactor": 2, - "legendFormat": "{{name}}", - "metric": "", - "refId": "A", - "step": 240 - }, - { - "expr": "sum(container_spec_memory_limit_bytes{name=~\".+\"}) by (name) ", - "hide": true, - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "B", - "step": 240 - }, - { - "exemplar": true, - "expr": "container_memory_usage_bytes{name!~\"prometheus|cadvisor|grafana|node-exporter\", name=~\".+\"} - container_memory_cache{name!~\"prometheus|cadvisor|grafana|node-exporter\", name=~\".+\"}", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "C", - "step": 240 - } - ], - "title": "Usage memory", - "transformations": [ - { - "id": "reduce", - "options": { - "includeTimeField": false, - "reducers": [ - "last" - ] - } - } - ], - "type": "table" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org1.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org1.example.com-fixed-asset" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org2.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org2.example.com-fixed-asset" - } - ] - } - ] - }, - "fill": 0, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 25 - }, - "hiddenSeries": false, - "id": 38, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_usage_seconds_total{name!~\"prometheus|cadvisor|grafana|node-exporter|\"}[$interval])) by (name) / count(node_cpu_seconds_total{mode=\"idle\"}) without (cpu,mode) * 100", - "hide": false, - "instant": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "metric": "", - "refId": "F", - "step": 240 - } - ], - "thresholds": [], - "timeRegions": [], - "title": "CPU Usage per Container", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1693", - "format": "percent", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:1694", - "format": "short", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org1.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org1.example.com-fixed-asset" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "dev-peer0.org2.example.com-fixed-asset-.*" - }, - "properties": [ - { - "id": "displayName", - "value": "dev-peer0.org2.example.com-fixed-asset" - } - ] - } - ] - }, - "fill": 0, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 35 - }, - "hiddenSeries": false, - "id": 39, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.1", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(container_memory_rss{name!~\"prometheus|cadvisor|grafana|node-exporter|\"}) by (name)", - "hide": true, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "A", - "step": 240 - }, - { - "exemplar": true, - "expr": "sum(container_memory_usage_bytes{name!~\"prometheus|cadvisor|grafana|node-exporter|\"}-container_memory_cache{name!~\"prometheus|cadvisor|grafana|node-exporter|\"}) by (name)", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{name}}", - "refId": "B", - "step": 240 - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Memory Usage per Container", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1101", - "format": "decbytes", - "label": "", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:1102", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 45 - }, - "id": 45, - "panels": [], - "title": "Chaincode Metrics", - "type": "row" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 0, - "y": 46 - }, - "id": 41, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "rate(chaincode_shim_request_duration_sum{chaincode!~\"_.*\"}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "chaincode_shim_request_duration_count{chaincode!~\"_.*\"}", - "hide": true, - "interval": "", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "chaincode_shim_request_duration_bucket{chaincode=~\"_.*\"}", - "hide": true, - "interval": "", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "C" - } - ], - "title": "Request Duration", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 8, - "y": 46 - }, - "id": 43, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "rate(chaincode_shim_requests_received{chaincode!~\"_.*\"}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "A" - } - ], - "title": "Request Received", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 16, - "y": 46 - }, - "id": 42, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "rate(chaincode_shim_requests_completed{chaincode!~\"_.*\"}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "A" - } - ], - "title": "Requests Completed", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 57 - }, - "id": 49, - "panels": [], - "title": "Endorser Metrics", - "type": "row" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "fixed-asset.* peer0.org2.example.com.*" - }, - "properties": [ - { - "id": "displayName", - "value": "fixed-asset-peer0.org2" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "fixed-asset.* peer0.org1.example.com.*" - }, - "properties": [ - { - "id": "displayName", - "value": "fixed-asset-peer0.org1" - } - ] - } - ] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 0, - "y": 58 - }, - "id": 50, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "rate(endorser_proposal_duration_sum{chaincode=~\"fixed-asset.*\"}[$interval]) / on (instance) rate(endorser_proposals_received{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "endorser_proposal_request_duration_count{chaincode=~\"fixed-asset.*\"}", - "hide": true, - "interval": "", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "endorser_proposal_request_duration_bucket{chaincode=~\"fixed-asset.*\"}", - "hide": true, - "interval": "", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "C" - } - ], - "title": "Proposal Duration", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "fixed-asset.* peer0.org2.example.com.*" - }, - "properties": [ - { - "id": "displayName", - "value": "fixed-asset-peer0.org2" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "fixed-asset.* peer0.org1.example.com.*" - }, - "properties": [ - { - "id": "displayName", - "value": "fixed-asset-peer0.org1" - } - ] - } - ] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 8, - "y": 58 - }, - "id": 55, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "exemplar": true, - "expr": "rate(endorser_proposals_received{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{chaincode}} {{instance}}", - "refId": "A" - } - ], - "title": "Proposal Received", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 8, - "x": 16, - "y": 58 - }, - "id": 52, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "exemplar": true, - "expr": "rate(endorser_successful_proposals{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{job}}", - "refId": "A" - } - ], - "title": "Successful Proposals", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 69 - }, - "id": 54, - "panels": [], - "title": "Ledger Metrics", - "type": "row" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 6, - "x": 0, - "y": 70 - }, - "id": 56, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "exemplar": true, - "expr": "rate(ledger_block_processing_time_sum{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{job}}", - "refId": "A" - }, - { - "exemplar": true, - "expr": "ledger_block_processing_time_count{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "B" - }, - { - "exemplar": true, - "expr": "ledger_block_processing_time_bucket{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "C" - } - ], - "title": "Block Processing Time", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 6, - "x": 6, - "y": 70 - }, - "id": 57, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "rate(ledger_blockstorage_commit_time_sum{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{job}}", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "ledger_blockstorage_commit_time_count{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "ledger_blockstorage_commit_time_bucket{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "C" - } - ], - "title": "Block Storage Commit Time", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 6, - "x": 12, - "y": 70 - }, - "id": 58, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "exemplar": true, - "expr": "rate(ledger_statedb_commit_time_sum{}[$interval])", - "hide": false, - "interval": "0.01m", - "legendFormat": "{{job}}", - "refId": "A" - }, - { - "exemplar": true, - "expr": "ledger_statedb_commit_time_count{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "B" - }, - { - "exemplar": true, - "expr": "ledger_statedb_commit_time_bucket{}", - "hide": true, - "interval": "", - "legendFormat": "{{job}}", - "refId": "C" - } - ], - "title": "StateDB Commit Time", - "type": "timeseries" - }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 6, - "x": 18, - "y": 70 - }, - "id": 61, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "ledger_blockchain_height{}", - "interval": "", - "legendFormat": "{{job}}", - "refId": "A" - } - ], - "title": "Blockchain height", - "type": "timeseries" - } - ], - "refresh": "5s", - "schemaVersion": 33, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": ".+", - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "definition": "", - "hide": 0, - "includeAll": true, - "label": "Container Group", - "multi": true, - "name": "containergroup", - "options": [], - "query": { - "query": "label_values(container_group)", - "refId": "Prometheus-containergroup-Variable-Query" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query", - "useTags": false - }, - { - "auto": true, - "auto_count": 50, - "auto_min": "50s", - "current": { - "selected": false, - "text": "auto", - "value": "$__auto_interval_interval" - }, - "hide": 0, - "includeAll": false, - "label": "Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": true, - "text": "auto", - "value": "$__auto_interval_interval" - }, - { - "selected": false, - "text": "30s", - "value": "30s" - }, - { - "selected": false, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "2m", - "value": "2m" - }, - { - "selected": false, - "text": "3m", - "value": "3m" - }, - { - "selected": false, - "text": "5m", - "value": "5m" - }, - { - "selected": false, - "text": "7m", - "value": "7m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "6h", - "value": "6h" - }, - { - "selected": false, - "text": "12h", - "value": "12h" - }, - { - "selected": false, - "text": "1d", - "value": "1d" - }, - { - "selected": false, - "text": "7d", - "value": "7d" - }, - { - "selected": false, - "text": "14d", - "value": "14d" - }, - { - "selected": false, - "text": "30d", - "value": "30d" - } - ], - "query": "30s,1m,2m,3m,5m,7m,10m,30m,1h,6h,12h,1d,7d,14d,30d", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - }, - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "definition": "", - "hide": 0, - "includeAll": false, - "label": "Node", - "multi": true, - "name": "server", - "options": [], - "query": { - "query": "label_values(node_boot_time, instance)", - "refId": "Prometheus-server-Variable-Query" - }, - "refresh": 1, - "regex": "/([^:]+):.*/", - "skipUrlSync": false, - "sort": 0, - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "browser", - "title": "HLF Performances Review", - "uid": "UeOYeJpWy", - "version": 2, - "weekStart": "" - } \ No newline at end of file + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Overview of the most important Fabric, system and Docker container metrics. ", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 893, + "graphTooltip": 1, + "iteration": 1643752938992, + "links": [], + "liveNow": false, + "panels": [ + { + "aliasColors": { + "{id=\"/\",instance=\"cadvisor:8080\",job=\"prometheus\"}": "#BA43A9" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 6, + "w": 5, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.4", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "100 - (avg(rate(node_cpu_seconds_total{job=\"node\",mode=\"idle\"}[$interval])) * 100)", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "E" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "System CPU Usage", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": false, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:7341", + "format": "percent", + "label": "", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:7342", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 3, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 6, + "w": 6, + "x": 5, + "y": 0 + }, + "hiddenSeries": false, + "id": 59, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.4", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum(container_memory_usage_bytes{name=~\".+\"}) - sum(container_memory_cache{name=~\".+\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Total Memory Usage by all containers", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1101", + "format": "decbytes", + "label": "", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:1102", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 11, + "y": 0 + }, + "id": 31, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.3.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "count(container_last_seen{name=~\".+\"})", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 1800 + } + ], + "title": "Containers", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 0.75 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 14, + "y": 0 + }, + "id": 26, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "8.3.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "min((node_filesystem_size_bytes - node_filesystem_free_bytes )/ node_filesystem_size_bytes)", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 1800 + } + ], + "title": "Disk space", + "type": "gauge" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 70 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 19, + "y": 0 + }, + "id": 25, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "8.3.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "((node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes) * 100", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 1800 + } + ], + "title": "Memory", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 47, + "panels": [], + "title": "Docker Containers Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 5, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 7 + }, + "hiddenSeries": false, + "id": 1, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.4", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "avg by (name)(rate(container_cpu_usage_seconds_total{name!~\"cadvisor|node-exporter|grafana|prometheus||\"}[$interval])) * 100", + "hide": false, + "interval": "", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Total CPU Usage per Container", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2189", + "format": "percent", + "label": "", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:2190", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 10, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.3.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "container_memory_usage_bytes{name!~\"cadvisor|node-exporter|grafana|prometheus||\"} - container_memory_cache{name!~\"cadvisor|node-exporter|grafana|prometheus||\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "refId": "B", + "step": 240 + } + ], + "title": "Total Memory Usage per Container", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "displayMode": "auto" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 10000000 + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 25000000 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 37, + "links": [], + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "8.3.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "container_memory_usage_bytes{name!~\"prometheus|cadvisor|grafana|node-exporter\", name=~\".+\"} - container_memory_cache{name!~\"prometheus|cadvisor|grafana|node-exporter\", name=~\".+\"}", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "refId": "C", + "step": 240 + } + ], + "title": "Usage memory", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "reducers": [ + "last" + ] + } + } + ], + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 25 + }, + "hiddenSeries": false, + "id": 38, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.4", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "avg by (name)(rate(container_cpu_usage_seconds_total{name!~\"cadvisor|node-exporter|grafana|prometheus||\"}[$interval])) * 100", + "hide": false, + "interval": "", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU Usage per Container", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1693", + "format": "percent", + "label": "", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:1694", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 35 + }, + "hiddenSeries": false, + "id": 39, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.4", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum by (name) (container_memory_usage_bytes{name!~\"prometheus|cadvisor|grafana|node-exporter|\"}-container_memory_cache{name!~\"prometheus|cadvisor|grafana|node-exporter|\"})", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "refId": "B", + "step": 240 + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Memory Usage per Container", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1101", + "format": "decbytes", + "label": "", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:1102", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 45, + "panels": [], + "title": "Chaincode Metrics", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 0, + "y": 46 + }, + "id": 41, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum by(job, chaincode) (rate(chaincode_shim_request_duration_sum{chaincode!~\"_lifecycle.*|cscc|qscc\"}[$interval])) / sum by(job, chaincode) (rate(chaincode_shim_request_duration_count{chaincode!~\"_lifecycle.*|cscc|qscc\"}[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": " {{job}} {{chaincode}}", + "refId": "A" + } + ], + "title": "Request Duration", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 8, + "y": 46 + }, + "id": 43, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum without(channel,type) (rate(chaincode_shim_requests_received{chaincode!~\"_lifecycle.*|cscc|qscc\"}[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}} {{chaincode}}", + "refId": "A" + } + ], + "title": "Request Received", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 46 + }, + "id": 42, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum by (chaincode,job) (rate(chaincode_shim_requests_completed{chaincode!~\"_lifecycle.*|cscc|qscc\"}[$interval]))", + "hide": false, + "interval": "0.01m", + "intervalFactor": 1, + "legendFormat": "{{job}} {{chaincode}}", + "refId": "A" + } + ], + "title": "Requests Completed", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 57 + }, + "id": 49, + "panels": [], + "title": "Endorser Metrics", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 0, + "y": 58 + }, + "id": 50, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum without(channel, chaincode) (rate(endorser_proposal_duration_sum{chaincode!~\"cscc|qscc|_lifecycle.*\",success=\"true\"}[$interval])) / sum without(channel, chaincode) (rate(endorser_proposal_duration_count{chaincode!~\"cscc|qscc|_lifecycle.*\",success=\"true\"}[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Successful Proposal Duration", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 8, + "y": 58 + }, + "id": 55, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "rate(endorser_proposals_received[$interval])", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Proposal Received", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 58 + }, + "id": 52, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "rate(endorser_successful_proposals[$interval])", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Successful Proposals", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 69 + }, + "id": 54, + "panels": [], + "title": "Ledger Metrics", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 6, + "x": 0, + "y": 70 + }, + "id": 56, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum without(channel) (rate(ledger_block_processing_time_sum[$interval])) / sum without(channel) (rate(ledger_block_processing_time_count[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Block Processing Time", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 6, + "x": 6, + "y": 70 + }, + "id": 57, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum without(channel) (rate(ledger_blockstorage_commit_time_sum[$interval])) / sum without(channel) (rate(ledger_blockstorage_commit_time_count[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Block Storage Commit Time", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 6, + "x": 12, + "y": 70 + }, + "id": 58, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "sum without(channel) (rate(ledger_statedb_commit_time_sum[$interval])) / sum without(channel) (rate(ledger_statedb_commit_time_count[$interval]))", + "hide": false, + "interval": "0.01m", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "StateDB Commit Time", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 6, + "x": 18, + "y": 70 + }, + "id": 61, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "exemplar": true, + "expr": "ledger_blockchain_height", + "interval": "", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "title": "Blockchain height", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 34, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "Container Group", + "multi": true, + "name": "containergroup", + "options": [], + "query": { + "query": "label_values(container_group)", + "refId": "Prometheus-containergroup-Variable-Query" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query", + "useTags": false + }, + { + "auto": true, + "auto_count": 50, + "auto_min": "50s", + "current": { + "selected": false, + "text": "auto", + "value": "$__auto_interval_interval" + }, + "hide": 0, + "includeAll": false, + "label": "Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_interval" + }, + { + "selected": false, + "text": "30s", + "value": "30s" + }, + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "2m", + "value": "2m" + }, + { + "selected": false, + "text": "3m", + "value": "3m" + }, + { + "selected": false, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "7m", + "value": "7m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "30s,1m,2m,3m,5m,7m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "queryValue": "", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "current": { + "selected": true, + "text": [ + "None" + ], + "value": [ + "" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Node", + "multi": true, + "name": "server", + "options": [], + "query": { + "query": "label_values(node_boot_time, instance)", + "refId": "Prometheus-server-Variable-Query" + }, + "refresh": 1, + "regex": "/([^:]+):.*/", + "skipUrlSync": false, + "sort": 0, + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "HLF Performances Review", + "uid": "UeOYeJpWy", + "version": 6, + "weekStart": "" +} \ No newline at end of file diff --git a/test-network/scripts/createChannel.sh b/test-network/scripts/createChannel.sh index e4d14f0b..96894258 100755 --- a/test-network/scripts/createChannel.sh +++ b/test-network/scripts/createChannel.sh @@ -13,6 +13,10 @@ VERBOSE="$4" : ${MAX_RETRY:="5"} : ${VERBOSE:="false"} +: ${CONTAINER_CLI:="docker"} +: ${CONTAINER_CLI_COMPOSE:="${CONTAINER_CLI}-compose"} +infoln "Using ${CONTAINER_CLI} and ${CONTAINER_CLI_COMPOSE}" + if [ ! -d "channel-artifacts" ]; then mkdir channel-artifacts fi @@ -70,7 +74,7 @@ joinChannel() { setAnchorPeer() { ORG=$1 - docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME + ${CONTAINER_CLI} exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME } FABRIC_CFG_PATH=${PWD}/configtx diff --git a/test-network/scripts/deployCCAAS.sh b/test-network/scripts/deployCCAAS.sh index 4a8e1b3a..33593a6a 100755 --- a/test-network/scripts/deployCCAAS.sh +++ b/test-network/scripts/deployCCAAS.sh @@ -23,6 +23,10 @@ VERBOSE=${12:-"false"} CCAAS_SERVER_PORT=9999 +: ${CONTAINER_CLI:="docker"} +: ${CONTAINER_CLI_COMPOSE:="${CONTAINER_CLI}-compose"} +infoln "Using ${CONTAINER_CLI} and ${CONTAINER_CLI_COMPOSE}" + println "executing with the following" println "- CHANNEL_NAME: ${C_GREEN}${CHANNEL_NAME}${C_RESET}" println "- CC_NAME: ${C_GREEN}${CC_NAME}${C_RESET}" @@ -109,15 +113,15 @@ buildDockerImages() { # build the docker container infoln "Building Chaincode-as-a-Service docker image '${CC_NAME}' '${CC_SRC_PATH}'" set -x - docker build -f $CC_SRC_PATH/Dockerfile -t ${CC_NAME}_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 $CC_SRC_PATH >&log.txt + ${CONTAINER_CLI} build -f $CC_SRC_PATH/Dockerfile -t ${CC_NAME}_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 $CC_SRC_PATH >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt - verifyResult $res "Docker buid of chaincode-as-a-service container failed" + verifyResult $res "Docker build of chaincode-as-a-service container failed" successln "Docker image '${CC_NAME}_ccaas_image:latest' built succesfully" else infoln "Not building docker image; this the command we would have run" - infoln "docker build -f $CC_SRC_PATH/Dockerfile -t ${CC_NAME}_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 $CC_SRC_PATH" + infoln " ${CONTAINER_CLI} build -f $CC_SRC_PATH/Dockerfile -t ${CC_NAME}_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 $CC_SRC_PATH" fi } @@ -126,13 +130,13 @@ startDockerContainer() { if [ "$CCAAS_DOCKER_RUN" = "true" ]; then infoln "Starting the Chaincode-as-a-Service docker container..." set -x - docker run --rm -d --name peer0org1_${CC_NAME}_ccaas \ + ${CONTAINER_CLI} run --rm -d --name peer0org1_${CC_NAME}_ccaas \ --network fabric_test \ -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:${CCAAS_SERVER_PORT} \ -e CHAINCODE_ID=$PACKAGE_ID -e CORE_CHAINCODE_ID_NAME=$PACKAGE_ID \ ${CC_NAME}_ccaas_image:latest - docker run --rm -d --name peer0org2_${CC_NAME}_ccaas \ + ${CONTAINER_CLI} run --rm -d --name peer0org2_${CC_NAME}_ccaas \ --network fabric_test \ -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:${CCAAS_SERVER_PORT} \ -e CHAINCODE_ID=$PACKAGE_ID -e CORE_CHAINCODE_ID_NAME=$PACKAGE_ID \ diff --git a/test-network/scripts/envVar.sh b/test-network/scripts/envVar.sh index ebc39a45..b2acfb2a 100755 --- a/test-network/scripts/envVar.sh +++ b/test-network/scripts/envVar.sh @@ -11,10 +11,10 @@ . scripts/utils.sh export CORE_PEER_TLS_ENABLED=true -export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt +export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem +export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem +export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem +export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem export ORDERER_ADMIN_TLS_SIGN_CERT=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt export ORDERER_ADMIN_TLS_PRIVATE_KEY=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key @@ -52,7 +52,7 @@ setGlobals() { fi } -# Set environment variables for use in the CLI container +# Set environment variables for use in the CLI container setGlobalsCLI() { setGlobals $1 diff --git a/test-network/scripts/setAnchorPeer.sh b/test-network/scripts/setAnchorPeer.sh index 80a4b207..743203eb 100755 --- a/test-network/scripts/setAnchorPeer.sh +++ b/test-network/scripts/setAnchorPeer.sh @@ -51,6 +51,7 @@ updateAnchorPeer() { ORG=$1 CHANNEL_NAME=$2 + setGlobalsCLI $ORG createAnchorPeerUpdate diff --git a/test-network/setOrgEnv.sh b/test-network/setOrgEnv.sh index b66074fb..abccbf87 100755 --- a/test-network/setOrgEnv.sh +++ b/test-network/setOrgEnv.sh @@ -15,10 +15,10 @@ set -o pipefail # Where am I? DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" -ORDERER_CA=${DIR}/test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -PEER0_ORG1_CA=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -PEER0_ORG2_CA=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -PEER0_ORG3_CA=${DIR}/test-network/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt +ORDERER_CA=${DIR}/test-network/organizations/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem +PEER0_ORG1_CA=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem +PEER0_ORG2_CA=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem +PEER0_ORG3_CA=${DIR}/test-network/organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem if [[ ${ORG,,} == "org1" || ${ORG,,} == "digibank" ]]; then @@ -26,14 +26,14 @@ if [[ ${ORG,,} == "org1" || ${ORG,,} == "digibank" ]]; then CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp CORE_PEER_ADDRESS=localhost:7051 - CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt + CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem elif [[ ${ORG,,} == "org2" || ${ORG,,} == "magnetocorp" ]]; then CORE_PEER_LOCALMSPID=Org2MSP CORE_PEER_MSPCONFIGPATH=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp CORE_PEER_ADDRESS=localhost:9051 - CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt + CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org1.example.com-cert.pem else echo "Unknown \"$ORG\", please choose Org1/Digibank or Org2/Magnetocorp" @@ -56,4 +56,4 @@ echo "CORE_PEER_MSPCONFIGPATH=${CORE_PEER_MSPCONFIGPATH}" echo "CORE_PEER_ADDRESS=${CORE_PEER_ADDRESS}" echo "CORE_PEER_TLS_ROOTCERT_FILE=${CORE_PEER_TLS_ROOTCERT_FILE}" -echo "CORE_PEER_LOCALMSPID=${CORE_PEER_LOCALMSPID}" \ No newline at end of file +echo "CORE_PEER_LOCALMSPID=${CORE_PEER_LOCALMSPID}" diff --git a/token-erc-721/README.md b/token-erc-721/README.md deleted file mode 100644 index 25dc143a..00000000 --- a/token-erc-721/README.md +++ /dev/null @@ -1,458 +0,0 @@ -# ERC-721 token scenario - -The ERC-721 token smart contract demonstrates how to create and transfer non-fungible tokens. -Non-fungible tokens represent ownership over digital or physical assets. Example assets are artworks, houses, tickets, etc. -Non-fungible tokens are distinguishable and we can track the ownership of each one separately. - -In ERC-721, there is an account for each participant that holds a balance of tokens. -A mint transaction creates a non-fungible token for an owner and adds one token in the owner's account. -A transfer transaction changes the ownership of a token from the current owner to a new owner. -The transfer also debits one token from the previous owner's account and credits one token to another account. - -In this sample it is assumed that only one organization (played by Org1) is in an issuer role and can mint new tokens into their account, while any organization can transfer tokens from their account to a recipient's account. -Accounts could be defined at the organization level or client identity level. In this sample accounts are defined at the client identity level, where every authorized client with an enrollment certificate from their organization implicitly has an account ID that matches their client ID. -The client ID is simply a base64-encoded concatenation of the issuer and subject from the client identity's enrollment certificate. The client ID can therefore be considered the account ID that is used as the payment address of a recipient. - -In this tutorial, you will mint and transfer tokens as follows: - -- A member of Org1 uses the `MintWithTokenURI` function to create a new non-fungible token into their account. The `MintWithTokenURI` smart contract function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and creates a non-fungible token associated with the client ID with the requested token ID. -- The same minter client will then use the `TransferFrom` function to transfer a non-fungible token with a requested token ID to the recipient's account. It is assumed that the recipient has provided their account ID to the transfer caller out of band. The recipient can then transfer tokens to other registered users in the same fashion. - -## Bring up the test network - -You can run the ERC-721 token transfer scenario using the Fabric test network. Open a command terminal and navigate to the test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial. -``` -cd fabric-samples/test-network -``` - -Run the following command to start the test network: -``` -./network.sh up createChannel -ca -``` - -The test network is deployed with two peer organizations. The `createChannel` flag deploys the network with a single channel named `mychannel` with Org1 and Org2 as channel members. -The -ca flag is used to deploy the network using certificate authorities. This allows you to use each organization's CA to register and enroll new users for this tutorial. - -## Deploy the smart contract to the channel - -You can use the test network script to deploy the ERC-721 token contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command: - -``` -./network.sh deployCC -ccn token_erc721 -ccp ../token-erc-721/chaincode-javascript/ -ccl javascript -``` - -The above command deploys the chaincode with short name `token_erc721`. The smart contract will use the default endorsement policy of majority of channel members. -Since the channel has two members, this implies that we'll need to get peer endorsements from 2 out of the 2 channel members. - -Now you are ready to call the deployed smart contract via peer CLI calls. But let's first create the client identities for our scenario. - -## Register identities - -The smart contract supports accounts owned by individual client identities from organizations that are members of the channel. In our scenario, the minter of the tokens will be a member of Org1, while the recipient will belong to Org2. To highlight the connection between the `GetClientIdentity().GetID()` API and the information within a user's certificate, we will register two new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key. - -First, we need to set the following environment variables to use the Fabric CA client (and subsequent commands). -``` -export PATH=${PWD}/../bin:${PWD}:$PATH -export FABRIC_CFG_PATH=$PWD/../config/ -``` - -The terminal we have been using will represent Org1. We will use the Org1 CA to create the minter identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script): -``` -export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/ -``` - -You can register a new minter client identity using the `fabric-ca-client` tool: -``` -fabric-ca-client register --caname ca-org1 --id.name minter --id.secret minterpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem -``` - -You can now generate the identity certificates and MSP folder by providing the minter's enroll name and secret to the enroll command: -``` -fabric-ca-client enroll -u https://minter:minterpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem -``` - -Run the command below to copy the Node OU configuration file into the minter identity MSP folder. -``` -cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp/config.yaml -``` - -Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 recipient identity. Set the Fabric CA client home to the MSP of the Org2 CA admin: -``` -cd fabric-samples/test-network -export PATH=${PWD}/../bin:${PWD}:$PATH -export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ -``` - -You can register a recipient client identity using the `fabric-ca-client` tool: -``` -fabric-ca-client register --caname ca-org2 --id.name recipient --id.secret recipientpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem -``` - -We can now enroll to generate the recipient's identity certificates and MSP folder: -``` -fabric-ca-client enroll -u https://recipient:recipientpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem -``` - -Run the command below to copy the Node OU configuration file into the recipient identity MSP folder. -``` -cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp/config.yaml -``` - -## Mint a non-fungible token - -Now that we have created the identity of the minter, we can invoke the smart contract to mint a non-fungible token. -Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter identity from Org1. -``` -export CORE_PEER_TLS_ENABLED=true -export CORE_PEER_LOCALMSPID="Org1MSP" -export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp -export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -export CORE_PEER_ADDRESS=localhost:7051 -export TARGET_TLS_OPTIONS="-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -``` - -The last environment variable above will be utilized within the CLI invoke commands to set the target peers for endorsement, and the target ordering service endpoint and TLS options. - -We can then invoke the smart contract to mint a non-fungible token with a unique token ID `101`: -``` -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"MintWithTokenURI","Args":["101", "https://example.com/nft101.json"]}' -``` - -The mint function validated that the client is a member of the minter organization, and then create a new non-fungible token for the minter. We can check the minter client's account balance by calling the `ClientAccountBalance` function. -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountBalance","Args":[]}' -``` - -The function queries the balance of the account associated with the minter client ID and returns: -``` -1 -``` - -We can also check the owner of the issued token by calling the `OwnerOf` function. -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"OwnerOf","Args":["101"]}' -``` - -The function queries the owner of the non-fungible token associated with the token ID and returns: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com -``` - -## Transfer a non-fungible token - -The minter intends to transfer a non-fungible token to the Org2 recipient, but first the Org2 recipient needs to provide their own account ID as the payment address. -A client can derive their account ID from their own public certificate, but to be sure the account ID is accurate, the contract has a `ClientAccountID` utility function that simply looks at the callers certificate and returns the calling client's ID, which will be used as the account ID. -Let's prepare the Org2 terminal by setting the environment variables for the Org2 recipient user. -``` -export FABRIC_CFG_PATH=$PWD/../config/ -export CORE_PEER_TLS_ENABLED=true -export CORE_PEER_LOCALMSPID="Org2MSP" -export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp -export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -export CORE_PEER_ADDRESS=localhost:9051 -``` - -Using the Org2 terminal, the Org2 recipient user can retrieve their own account ID: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountID","Args":[]}' -``` - -The function returns of recipient's client ID. -The result shows that the subject and issuer is indeed the recipient user from Org2: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com -``` - -After the Org2 recipient provides their account ID to the minter, the minter can initiate a transfer from their account to the recipient's account. - -To transfer a non-fungible token, minter also needs to provide it's own account ID. -Back in the Org1 terminal, the Org1 minter user can retrieve their own account ID: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountID","Args":[]}' -``` - -The function returns of minter's client ID. -The result shows that the subject and issuer is indeed the recipient user from Org1: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com -``` - -After that, request the transfer of a non-fungible token `101` to the recipient account: -``` -export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" -export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com" -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"TransferFrom","Args":["'"$MINTER"'", "'"$RECIPIENT"'","101"]}' -``` - -The `TransferFrom` function validates ownership of the given non-fungible token. -It will then change the ownership of the non-fungible token from the current owner to the recipient. -It will also debit the caller's account and credit the recipient's account. Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist. - -While still in the Org1 terminal, let's request the minter's account balance again: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountBalance","Args":[]}' -``` - -The function queries the balance of the account associated with the minter client ID and returns: -``` -0 -``` - -And then using the Org2 terminal, let's request the recipient's balance: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountBalance","Args":[]}' -``` - -The function queries the balance of the account associated with the recipient client ID and returns: -``` -1 -``` - -While still in the Org2 terminal, let's check the current owner of the token. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"OwnerOf","Args":["101"]}' -``` - -The function queries the owner of the non-fungible token with the token ID and returns: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com -``` - -Congratulations, you've transferred a non-fungible token! The Org2 recipient can now transfer the token to other registered users in the same manner. - -## 3rd party transfers (Specific Token) - -This sample has another option, which allows an approved 3rd party operator to transfer a non-fungible token on behalf of the token owner. The owner appoves only one specific token to be transferred by the operator. This scenario demonstrates how to approve the operator and transfer a non-fungible token. - -In this scenario, you will approve the operator and transfer a specific non-fungible token as follows: - -- A minter has already created a non-fungible token according to the scenario above. -- The same minter client uses the `Approve` function to give the permission for an operator client to transfer a non-fungible token which has a specific token ID on behalf of the minter. It is assumed that the operator has provided their client ID to the `Approve` caller out of band. -- The operator client will then use the `TransferFrom` function to transfer the non-fungible token to the recipient's account on behalf of the minter. It is assumed that the recipient has provided their client ID to the `TransferFrom` caller out of band. - -## Register identity for 3rd party operator - -You have already brought up the network and deployed the smart contract to the channel. We will use the same network and smart contract. - -We will use the Org1 CA to create the operator identity. -Back in the Org1 terminal, you can register a new operator client identity using the `fabric-ca-client` tool: -``` -fabric-ca-client register --caname ca-org1 --id.name operator --id.secret operatorpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem -``` - -You can now generate the identity certificates and MSP folder by providing the operator's enroll name and secret to the enroll command: -``` -fabric-ca-client enroll -u https://operator:operatorpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/operator@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem -``` - -Run the command below to copy the Node OU configuration file into the operator identity MSP folder. -``` -cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/operator@org1.example.com/msp/config.yaml -``` - -## Approve an operator - -The minter intends to approve a non-fungible token to be transferred by the operator, but first the operator needs to provide their own client ID as the payment address. - -Open a 3rd terminal to represent the operator in Org1 and navigate to fabric-samples/test-network. Set the the environment variables for the Org1 operator user. - -``` -export PATH=${PWD}/../bin:${PWD}:$PATH -export FABRIC_CFG_PATH=$PWD/../config/ -export CORE_PEER_TLS_ENABLED=true -export CORE_PEER_LOCALMSPID="Org1MSP" -export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/operator@org1.example.com/msp -export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -export CORE_PEER_ADDRESS=localhost:7051 -export TARGET_TLS_OPTIONS="-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -``` - -Now the Org1 operator can retrieve their own client ID: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountID","Args":[]}' -``` - -The function returns of operator's client ID. -The result shows that the subject and issuer is indeed the operator user from Org1: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=operator::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com -``` - -After the Org1 operator provides their client ID to the minter, the minter can approve an operator. -Back in the Org1 minter terminal, issue a new non-fungible token with the token ID `102`. -And then request the approval for the operator to transfer the token. - -``` -# Issue a new token -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"MintWithTokenURI","Args":["102", "https://example.com/nft102.json"]}' - -# The owner approves -export OPERATOR="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=operator::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"Approve","Args":["'"$OPERATOR"'", "102"]}' -``` - -The approve function specified that the operator client can transfer the non-fungible token with the given token ID on behalf of the minter. We can check the operator client's approval by calling the `GetApproved` function. - -Let's request the operator's approval from the Org1 minter terminal. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"GetApproved","Args":["102"]}' -``` - -The function queries the approval associated with the operator client ID and returns: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=operator::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com -``` - -## Transfer a non-fungible token - -The operator intends to transfer a non-fungible token to the Org2 recipient on behalf of the minter. The operator has already got the minter client Id and the recipient client ID. - -Back in the 3rd operator terminal, request the transfer of a non-fungible token `102` to the recipient account: - -``` -export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" -export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com" -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "102"]}' -``` - -The `TransferFrom` function validates that the account associated with the calling client ID has the permission to transfer the given token on behalf of the current owner. -It will then change the ownership of the non-fungible token from the current owner to the recipient. -It will also debit the previous owner's account and credit the recipient's account. -It will also remove the operator's permission for this non-fungible token approved by the minter. -Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist. - -While still in the 3rd operator terminal for the operator, let's request the minter's account balance again: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"BalanceOf","Args":["'"$MINTER"'"]}' -``` - -The function queries the balance of the account associated with the minter client ID and returns: -``` -0 -``` - -And then, let's check the current owner of the token. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"OwnerOf","Args":["102"]}' -``` - -The function queries the owner of the non-fungible token with the token ID and returns: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com -``` - -While still in the 3rd operator terminal for the operator, let's request the operator's approval from the minter again. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"GetApproved","Args":["102"]}' -``` - -The function queries the approval associated with the operator client ID and returns no value. - -And then using the Org2 terminal, let's request the recipient's balance: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountBalance","Args":[]}' -``` - -The function queries the balance of the account associated with the recipient client ID and returns: -``` -2 -``` - -Congratulations, you've transferred a non-fungible token! The Org2 recipient can now transfer tokens to other registered users in the same manner. - - -## 3rd party transfers (All tokens) - -This sample has another option, which allows an approved 3rd party operator to transfer all non-fungible tokens on behalf of the token owner. The owner approves all tokens to be transferred by the operator. This scenario demonstrates how to approve the operator and transfer non-fungible tokens. - -In this scenario, you will approve the operator and transfer a non-fungible token as follows: - -- A minter has already created a non-fungible token according to the scenario above. -- The same minter client uses the `SetApprovalForAll` function to give the permission for an operator client to transfer all non-fungible tokens on behalf of the minter. It is assumed that the operator has provided their client ID to the `SetApprovalForAll` caller out of band. -- The operator client will then use the `TransferFrom` function to transfer the non-fungible token to the recipient's account on behalf of the minter. It is assumed that the recipient has provided their client ID to the `TransferFrom` caller out of band. - -## Approve an operator - -Request the approval for the operator to transfer the token. -we assume that the minter has already got the operator client ID as the payment address. - -Back in the Org1 minter terminal, request the approval for the operator to transfer all tokens on behalf of the original owner. -``` -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"SetApprovalForAll","Args":["'"$OPERATOR"'", "true"]}' -``` - -The `SetApprovalForAll` function specified that the operator client can transfer any non-fungible tokens on behalf of the minter. We can check the operator client's approval by calling the `IsApprovedForAll` function. - -Let's request the operator's approval from the Org1 minter terminal. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"IsApprovedForAll","Args":["'"$MINTER"'", "'"$OPERATOR"'"]}' -``` - -The function queries the approval associated with the operator client ID and returns: -``` -true -``` - -## Transfer a non-fungible token - -The operator intends to transfer a non-fungible token to the Org2 recipient on behalf of the minter. The operator has already got the minter client Id and the recipient client ID. - -Still in the Org1 minter terminal, issue a new non-fungible token with the token ID `103`. -``` -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"MintWithTokenURI","Args":["103", "https://example.com/nft103.json"]}' - -``` - -Back in the 3rd operator terminal, request the transfer of a non-fungible token `103` to the recipient account: - -``` -peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc721 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "103"]}' -``` - -The `TransferFrom` function validates that the account associated with the calling client ID has the permission to transfer tokens on behalf of the current owner. -It will then change the ownership of the non-fungible token and update balances like the other options. - -While still in the 3rd operator terminal for the operator, let's request the minter's account balance again: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"BalanceOf","Args":["'"$MINTER"'"]}' -``` - -The function queries the balance of the account associated with the minter client ID and returns: -``` -0 -``` - -And then, let's check the current owner of the token. - -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"OwnerOf","Args":["103"]}' -``` - -The function queries the owner of the non-fungible token with the token ID and returns: -``` -x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com -``` - -And then using the Org2 terminal, let's request the recipient's balance: -``` -peer chaincode query -C mychannel -n token_erc721 -c '{"function":"ClientAccountBalance","Args":[]}' -``` - -The function queries the balance of the account associated with the recipient client ID and returns: -``` -3 -``` - -Congratulations, you've transferred a non-fungible token! The Org2 recipient can now transfer tokens to other registered users in the same manner. - -## 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: -``` -./network.sh down -``` \ No newline at end of file diff --git a/token-erc-721/chaincode-go/chaincode/erc721-contract.go b/token-erc-721/chaincode-go/chaincode/erc721-contract.go new file mode 100644 index 00000000..94257a13 --- /dev/null +++ b/token-erc-721/chaincode-go/chaincode/erc721-contract.go @@ -0,0 +1,619 @@ +package chaincode + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +// Define objectType names for prefix +const balancePrefix = "balance" +const nftPrefix = "nft" +const approvalPrefix = "approval" + +// Define key names for options +const nameKey = "name" +const symbolKey = "symbol" + +// TokenERC721Contract contract for managing CRUD operations +type TokenERC721Contract struct { + contractapi.Contract +} + +func _readNFT(ctx contractapi.TransactionContextInterface, tokenId string) (*Nft, error) { + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + return nil, fmt.Errorf("failed to CreateCompositeKey %s: %v", tokenId, err) + } + + nftBytes, err := ctx.GetStub().GetState(nftKey) + if err != nil { + return nil, fmt.Errorf("failed to GetState %s: %v", tokenId, err) + } + + nft := new(Nft) + err = json.Unmarshal(nftBytes, nft) + if err != nil { + return nil, fmt.Errorf("failed to Unmarshal nftBytes: %v", err) + } + + return nft, nil +} + +func _nftExists(ctx contractapi.TransactionContextInterface, tokenId string) bool { + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + panic("error creating CreateCompositeKey:" + err.Error()) + } + + nftBytes, err := ctx.GetStub().GetState(nftKey) + if err != nil { + panic("error GetState nftBytes:" + err.Error()) + } + + return len(nftBytes) > 0 +} + +// BalanceOf counts all non-fungible tokens assigned to an owner +// param owner {String} An owner for whom to query the balance +// returns {int} The number of non-fungible tokens owned by the owner, possibly zero +func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterface, owner string) int { + // There is a key record for every non-fungible token in the format of balancePrefix.owner.tokenId. + // BalanceOf() queries for and counts all records matching balancePrefix.owner.* + + iterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{owner}) + if err != nil { + panic("Error creating asset chaincode:" + err.Error()) + } + + // Count the number of returned composite keys + balance := 0 + for iterator.HasNext() { + _, err := iterator.Next() + if err != nil { + return 0 + } + balance++ + + } + return balance +} + +// OwnerOf finds the owner of a non-fungible token +// param {String} tokenId The identifier for a non-fungible token +// returns {String} Return the owner of the non-fungible token +func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return "", fmt.Errorf("could not process OwnerOf for tokenId: %w", err) + } + + return nft.Owner, nil +} + +// Approve changes or reaffirms the approved client for a non-fungible token +// param {String} operator The new approved client +// param {String} tokenId the non-fungible token to approve +// returns {Boolean} Return whether the approval was successful or not +func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterface, operator string, tokenId string) (bool, error) { + sender64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return false, fmt.Errorf("failed to GetClientIdentity: %v", err) + } + + senderBytes, err := base64.StdEncoding.DecodeString(sender64) + if err != nil { + return false, fmt.Errorf("failed to DecodeString senderBytes: %v", err) + } + sender := string(senderBytes) + + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return false, fmt.Errorf("failed to _readNFT: %v", err) + } + + // Check if the sender is the current owner of the non-fungible token + // or an authorized operator of the current owner + owner := nft.Owner + operatorApproval, err := c.IsApprovedForAll(ctx, owner, sender) + if err != nil { + return false, fmt.Errorf("failed to get IsApprovedForAll: %v", err) + } + if owner != sender && !operatorApproval { + return false, fmt.Errorf("the sender is not the current owner nor an authorized operator") + } + + // Update the approved operator of the non-fungible token + nft.Approved = operator + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey %s: %v", nftKey, err) + } + + nftBytes, err := json.Marshal(nft) + if err != nil { + return false, fmt.Errorf("failed to marshal nftBytes: %v", err) + } + + err = ctx.GetStub().PutState(nftKey, nftBytes) + if err != nil { + return false, fmt.Errorf("failed to PutState for nftKey: %v", err) + } + + return true, nil +} + +// SetApprovalForAll enables or disables approval for a third party ("operator") +// to manage all the message sender's assets +// param {String} operator A client to add to the set of authorized operators +// param {Boolean} approved True if the operator is approved, false to revoke approval +// returns {Boolean} Return whether the approval was successful or not +func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) (bool, error) { + sender64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return false, fmt.Errorf("failed to GetClientIdentity: %v", err) + } + + senderBytes, err := base64.StdEncoding.DecodeString(sender64) + if err != nil { + return false, fmt.Errorf("failed to DecodeString sender: %v", err) + } + sender := string(senderBytes) + + nftApproval := new(Approval) + nftApproval.Owner = sender + nftApproval.Operator = operator + nftApproval.Approved = approved + + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{sender, operator}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey: %v", err) + } + + approvalBytes, err := json.Marshal(nftApproval) + if err != nil { + return false, fmt.Errorf("failed to marshal approvalBytes: %v", err) + } + + err = ctx.GetStub().PutState(approvalKey, approvalBytes) + if err != nil { + return false, fmt.Errorf("failed to PutState approvalBytes: %v", err) + } + + // Emit the ApprovalForAll event + err = ctx.GetStub().SetEvent("ApprovalForAll", approvalBytes) + if err != nil { + return false, fmt.Errorf("failed to SetEvent ApprovalForAll: %v", err) + } + + return true, nil +} + +// IsApprovedForAll returns if a client is an authorized operator for another client +// param {String} owner The client that owns the non-fungible tokens +// param {String} operator The client that acts on behalf of the owner +// returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise +func (c *TokenERC721Contract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, owner string, operator string) (bool, error) { + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{owner, operator}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey: %v", err) + } + approvalBytes, err := ctx.GetStub().GetState(approvalKey) + if err != nil { + return false, fmt.Errorf("failed to GetState approvalBytes %s: %v", approvalBytes, err) + } + + if len(approvalBytes) < 1 { + return false, nil + } + + approval := new(Approval) + err = json.Unmarshal(approvalBytes, approval) + if err != nil { + return false, fmt.Errorf("failed to Unmarshal: %v, string %s", err, string(approvalBytes)) + } + + return approval.Approved, nil + +} + +// GetApproved returns the approved client for a single non-fungible token +// param {String} tokenId the non-fungible token to find the approved client for +// returns {Object} Return the approved client for this non-fungible token, or null if there is none +func (c *TokenERC721Contract) GetApproved(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return "false", fmt.Errorf("failed GetApproved for tokenId : %v", err) + } + return nft.Approved, nil +} + +// TransferFrom transfers the ownership of a non-fungible token +// from one owner to another owner +// param {String} from The current owner of the non-fungible token +// param {String} to The new owner +// param {String} tokenId the non-fungible token to transfer +// returns {Boolean} Return whether the transfer was successful or not + +func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, tokenId string) (bool, error) { + + // Get ID of submitting client identity + sender64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return false, fmt.Errorf("failed to GetClientIdentity: %v", err) + } + + senderBytes, err := base64.StdEncoding.DecodeString(sender64) + if err != nil { + return false, fmt.Errorf("failed to DecodeString sender: %v", err) + } + sender := string(senderBytes) + + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return false, fmt.Errorf("failed to _readNFT : %v", err) + } + + owner := nft.Owner + operator := nft.Approved + operatorApproval, err := c.IsApprovedForAll(ctx, owner, sender) + if err != nil { + return false, fmt.Errorf("failed to get IsApprovedForAll : %v", err) + } + if owner != sender && operator != sender && !operatorApproval { + return false, fmt.Errorf("the sender is not the current owner nor an authorized operator") + } + + // Check if `from` is the current owner + if owner != from { + return false, fmt.Errorf("the from is not the current owner") + } + + // Clear the approved client for this non-fungible token + nft.Approved = "" + + // Overwrite a non-fungible token to assign a new owner. + nft.Owner = to + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey: %v", err) + } + + nftBytes, err := json.Marshal(nft) + if err != nil { + return false, fmt.Errorf("failed to marshal approval: %v", err) + } + + err = ctx.GetStub().PutState(nftKey, nftBytes) + if err != nil { + return false, fmt.Errorf("failed to PutState nftBytes %s: %v", nftBytes, err) + } + + // Remove a composite key from the balance of the current owner + balanceKeyFrom, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{from, tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey from: %v", err) + } + + err = ctx.GetStub().DelState(balanceKeyFrom) + if err != nil { + return false, fmt.Errorf("failed to DelState balanceKeyFrom %s: %v", nftBytes, err) + } + + // Save a composite key to count the balance of a new owner + balanceKeyTo, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{to, tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey to: %v", err) + } + err = ctx.GetStub().PutState(balanceKeyTo, []byte{0}) + if err != nil { + return false, fmt.Errorf("failed to PutState balanceKeyTo %s: %v", balanceKeyTo, err) + } + + // Emit the Transfer event + transferEvent := new(Transfer) + transferEvent.From = from + transferEvent.To = to + transferEvent.TokenId = tokenId + + transferEventBytes, err := json.Marshal(transferEvent) + if err != nil { + return false, fmt.Errorf("failed to marshal transferEventBytes: %v", err) + } + + err = ctx.GetStub().SetEvent("Transfer", transferEventBytes) + if err != nil { + return false, fmt.Errorf("failed to SetEvent transferEventBytes %s: %v", transferEventBytes, err) + } + return true, nil +} + +// ============== ERC721 metadata extension =============== + +// Name returns a descriptive name for a collection of non-fungible tokens in this contract +// returns {String} Returns the name of the token + +func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface) (string, error) { + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return "", fmt.Errorf("failed to get Name bytes: %s", err) + } + + return string(bytes), nil +} + +// Symbol returns an abbreviated name for non-fungible tokens in this contract. +// returns {String} Returns the symbol of the token + +func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { + bytes, err := ctx.GetStub().GetState(symbolKey) + if err != nil { + return "", fmt.Errorf("failed to get Symbol: %v", err) + } + + return string(bytes), nil +} + +// TokenURI returns a distinct Uniform Resource Identifier (URI) for a given token. +// param {string} tokenId The identifier for a non-fungible token +// returns {String} Returns the URI of the token + +func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return "", fmt.Errorf("failed to get TokenURI: %v", err) + } + return nft.TokenURI, nil +} + +// ============== ERC721 enumeration extension =============== +// TotalSupply counts non-fungible tokens tracked by this contract. +// +// @param {Context} ctx the transaction context +// @returns {Number} Returns a count of valid non-fungible tokens tracked by this contract, +// where each one of them has an assigned and queryable owner. + +func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInterface) int { + // There is a key record for every non-fungible token in the format of nftPrefix.tokenId. + // TotalSupply() queries for and counts all records matching nftPrefix.* + + iterator, err := ctx.GetStub().GetStateByPartialCompositeKey(nftPrefix, []string{}) + if err != nil { + panic("Error creating GetStateByPartialCompositeKey:" + err.Error()) + } + // Count the number of returned composite keys + + totalSupply := 0 + for iterator.HasNext() { + _, err := iterator.Next() + if err != nil { + return 0 + } + totalSupply++ + + } + return totalSupply + +} + +// ============== ERC721 enumeration extension =============== +// Set optional information for a token. +// param {String} name The name of the token +// param {String} symbol The symbol of the token + +func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) { + // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return false, fmt.Errorf("failed to get clientMSPID: %v", err) + } else if clientMSPID != "Org1MSP" { + return false, fmt.Errorf("client is not authorized to set the name and symbol of the token") + } + + err = ctx.GetStub().PutState(nameKey, []byte(name)) + if err != nil { + return false, fmt.Errorf("failed to PutState nameKey %s: %v", nameKey, err) + } + + err = ctx.GetStub().PutState(symbolKey, []byte(symbol)) + if err != nil { + return false, fmt.Errorf("failed to PutState symbolKey %s: %v", symbolKey, err) + } + + return true, nil +} + +// Mint a new non-fungible token +// param {String} tokenId Unique ID of the non-fungible token to be minted +// param {String} tokenURI URI containing metadata of the minted non-fungible token +// returns {Object} Return the non-fungible token object + +func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContextInterface, tokenId string, tokenURI string) (*Nft, error) { + + // Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return nil, fmt.Errorf("failed to get clientMSPID: %v", err) + } + + if clientMSPID != "Org1MSP" { + return nil, fmt.Errorf("client is not authorized to set the name and symbol of the token") + } + + // Get ID of submitting client identity + minter64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return nil, fmt.Errorf("failed to get minter id: %v", err) + } + + minterBytes, err := base64.StdEncoding.DecodeString(minter64) + if err != nil { + return nil, fmt.Errorf("failed to DecodeString minter64: %v", err) + } + minter := string(minterBytes) + + // Check if the token to be minted does not exist + exists := _nftExists(ctx, tokenId) + if exists { + return nil, fmt.Errorf("the token %s is already minted.: %v", tokenId, err) + } + + // Add a non-fungible token + nft := new(Nft) + nft.TokenId = tokenId + nft.Owner = minter + nft.TokenURI = tokenURI + + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + return nil, fmt.Errorf("failed to CreateCompositeKey to nftKey: %v", err) + } + + nftBytes, err := json.Marshal(nft) + if err != nil { + return nil, fmt.Errorf("failed to marshal nft: %v", err) + } + + err = ctx.GetStub().PutState(nftKey, nftBytes) + if err != nil { + return nil, fmt.Errorf("failed to PutState nftBytes %s: %v", nftBytes, err) + } + + // A composite key would be balancePrefix.owner.tokenId, which enables partial + // composite key query to find and count all records matching balance.owner.* + // An empty value would represent a delete, so we simply insert the null character. + + balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{minter, tokenId}) + if err != nil { + return nil, fmt.Errorf("failed to CreateCompositeKey to balanceKey: %v", err) + } + + err = ctx.GetStub().PutState(balanceKey, []byte{'\u0000'}) + if err != nil { + return nil, fmt.Errorf("failed to PutState balanceKey %s: %v", nftBytes, err) + } + + // Emit the Transfer event + transferEvent := new(Transfer) + transferEvent.From = "0x0" + transferEvent.To = minter + transferEvent.TokenId = tokenId + + transferEventBytes, err := json.Marshal(transferEvent) + if err != nil { + return nil, fmt.Errorf("failed to marshal transferEventBytes: %v", err) + } + + err = ctx.GetStub().SetEvent("Transfer", transferEventBytes) + if err != nil { + return nil, fmt.Errorf("failed to SetEvent transferEventBytes %s: %v", transferEventBytes, err) + } + + return nft, nil +} + +// Burn a non-fungible token +// param {String} tokenId Unique ID of a non-fungible token +// returns {Boolean} Return whether the burn was successful or not +func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface, tokenId string) (bool, error) { + owner64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return false, fmt.Errorf("failed to GetClientIdentity owner64: %v", err) + } + + ownerBytes, err := base64.StdEncoding.DecodeString(owner64) + if err != nil { + return false, fmt.Errorf("failed to DecodeString owner64: %v", err) + } + owner := string(ownerBytes) + + // Check if a caller is the owner of the non-fungible token + nft, err := _readNFT(ctx, tokenId) + if err != nil { + return false, fmt.Errorf("failed to _readNFT nft : %v", err) + } + if nft.Owner != owner { + return false, fmt.Errorf("non-fungible token %s is not owned by %s", tokenId, owner) + } + + // Delete the token + nftKey, err := ctx.GetStub().CreateCompositeKey(nftPrefix, []string{tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey tokenId: %v", err) + } + + err = ctx.GetStub().DelState(nftKey) + if err != nil { + return false, fmt.Errorf("failed to DelState nftKey: %v", err) + } + + // Remove a composite key from the balance of the owner + balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{owner, tokenId}) + if err != nil { + return false, fmt.Errorf("failed to CreateCompositeKey balanceKey %s: %v", balanceKey, err) + } + + err = ctx.GetStub().DelState(balanceKey) + if err != nil { + return false, fmt.Errorf("failed to DelState balanceKey %s: %v", balanceKey, err) + } + + // Emit the Transfer event + transferEvent := new(Transfer) + transferEvent.From = owner + transferEvent.To = "0x0" + transferEvent.TokenId = tokenId + + transferEventBytes, err := json.Marshal(transferEvent) + if err != nil { + return false, fmt.Errorf("failed to marshal transferEventBytes: %v", err) + } + + err = ctx.GetStub().SetEvent("Transfer", transferEventBytes) + if err != nil { + return false, fmt.Errorf("failed to SetEvent transferEventBytes: %v", err) + } + + return true, nil +} + +// ClientAccountBalance returns the balance of the requesting client's account. +// returns {Number} Returns the account balance +func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { + // Get ID of submitting client identity + clientAccountID64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return 0, fmt.Errorf("failed to GetClientIdentity minter: %v", err) + } + + clientAccountIDBytes, err := base64.StdEncoding.DecodeString(clientAccountID64) + if err != nil { + return 0, fmt.Errorf("failed to DecodeString sender: %v", err) + } + + clientAccountID := string(clientAccountIDBytes) + + return c.BalanceOf(ctx, clientAccountID), nil +} + +// ClientAccountID returns the id of the requesting client's account. +// In this implementation, the client account ID is the clientId itself. +// Users can use this function to get their own account id, which they can then give to others as the payment address + +func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + // Get ID of submitting client identity + clientAccountID64, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("failed to GetClientIdentity minter: %v", err) + } + + clientAccountBytes, err := base64.StdEncoding.DecodeString(clientAccountID64) + if err != nil { + return "", fmt.Errorf("failed to DecodeString clientAccount64: %v", err) + } + clientAccount := string(clientAccountBytes) + + return clientAccount, nil +} diff --git a/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go b/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go new file mode 100644 index 00000000..cbb88c67 --- /dev/null +++ b/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go @@ -0,0 +1,282 @@ +package chaincode + +import ( + "encoding/base64" + "testing" + + "github.com/hyperledger/fabric-chaincode-go/pkg/cid" + "github.com/hyperledger/fabric-chaincode-go/shim" + "github.com/hyperledger/fabric-contract-api-go/contractapi" + "github.com/hyperledger/fabric-protos-go/ledger/queryresult" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const owner = "x509::CN=minter,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US" +const operator = "x509::CN=org,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=AR" + +type MockStub struct { + shim.ChaincodeStubInterface + mock.Mock +} + +func (ms *MockStub) GetStateByPartialCompositeKey(objectType string, keys []string) (shim.StateQueryIteratorInterface, error) { + args := ms.Called(objectType, keys) + return args.Get(0).(shim.StateQueryIteratorInterface), args.Error(1) +} + +func (ms *MockStub) GetState(key string) ([]byte, error) { + args := ms.Called(key) + return args.Get(0).([]byte), args.Error(1) +} + +func (ms *MockStub) PutState(key string, value []byte) error { + args := ms.Called(key, value) + return args.Error(0) +} +func (ms *MockStub) SetEvent(key string, value []byte) error { + args := ms.Called(key, value) + return args.Error(0) +} + +func (ms *MockStub) DelState(key string) error { + args := ms.Called(key) + return args.Error(0) +} + +func (ms *MockStub) CreateCompositeKey(objectType string, attributes []string) (string, error) { + args := ms.Called(objectType, attributes) + return args.Get(0).(string), args.Error(1) +} + +type MockClientIdentity struct { + cid.ClientIdentity + mock.Mock +} + +func (mci *MockClientIdentity) GetID() (string, error) { + args := mci.Called() + return args.Get(0).(string), args.Error(1) +} + +func (mci *MockClientIdentity) GetMSPID() (string, error) { + args := mci.Called() + return args.Get(0).(string), args.Error(1) +} + +func (mc *MockContext) GetStub() shim.ChaincodeStubInterface { + args := mc.Called() + return args.Get(0).(*MockStub) +} + +type MockContext struct { + contractapi.TransactionContextInterface + mock.Mock +} + +func (mc *MockContext) GetClientIdentity() cid.ClientIdentity { + args := mc.Called() + return args.Get(0).(*MockClientIdentity) +} + +type MockIterator struct { + shim.StateQueryIteratorInterface + queryresult.KV +} + +func (it *MockIterator) HasNext() bool { + return false +} + +func setupStub() (*MockContext, *MockStub) { + balancePrefix := "balance" + approvalPrefix := "approval" + nftPrefix := "nft" + mockTokenId := "101" + anyString := mock.AnythingOfType("string") + anyUint8Slice := mock.AnythingOfType("[]uint8") + nftStr := "{\"tokenId\":\"101\",\"owner\":\"" + owner + "\",\"tokenURI\":\"https://example.com/nft101.json\",\"approved\":\"" + operator + "\"}" + approvalStr := "{\"owner\":\"" + owner + "\",\"operator\":\"" + owner + "\",\"approved\":true}" + + ms := new(MockStub) + iterator := new(MockIterator) + + ms.On("GetStateByPartialCompositeKey", balancePrefix, []string{owner}).Return(iterator, nil) + ms.On("GetStateByPartialCompositeKey", nftPrefix, []string{}).Return(iterator, nil) + + ms.On("CreateCompositeKey", nftPrefix, []string{mockTokenId}).Return("nft101", nil) + ms.On("CreateCompositeKey", nftPrefix, []string{"102"}).Return("nft102", nil) + ms.On("CreateCompositeKey", approvalPrefix, []string{owner, owner}).Return(approvalPrefix+owner+owner, nil) + ms.On("CreateCompositeKey", approvalPrefix, []string{owner, operator}).Return(approvalPrefix+owner+operator, nil) + ms.On("CreateCompositeKey", balancePrefix, []string{owner, mockTokenId}).Return(balancePrefix+owner+mockTokenId, nil) + ms.On("CreateCompositeKey", balancePrefix, []string{operator, mockTokenId}).Return(balancePrefix+operator+mockTokenId, nil) + ms.On("CreateCompositeKey", balancePrefix, []string{owner, "102"}).Return(balancePrefix+owner+mockTokenId, nil) + + ms.On("GetState", "nft101").Return([]byte(nftStr), nil) + ms.On("GetState", "nft102").Return([]uint8{}, nil) + ms.On("GetState", approvalPrefix+owner+owner).Return([]byte(approvalStr), nil) + ms.On("GetState", "name").Return([]byte("lala"), nil) + ms.On("GetState", "symbol").Return([]byte("lelo"), nil) + + ms.On("PutState", "name", []byte("someName")).Return(nil) + ms.On("PutState", "symbol", []byte("someSymbol")).Return(nil) + ms.On("PutState", anyString, anyUint8Slice).Return(nil) + ms.On("PutState", balancePrefix+owner+"101", []byte{0}).Return(nil) + ms.On("PutState", balancePrefix+owner+"102", []byte{'\u0000'}).Return(nil) + ms.On("PutState", "nft101", []byte("nft101")).Return(nil) + ms.On("PutState", "nft102", []byte("nft102")).Return(nil) + + ms.On("SetEvent", "ApprovalForAll", anyUint8Slice).Return(nil) + ms.On("SetEvent", "Transfer", anyUint8Slice).Return(nil) + + ms.On("DelState", anyString).Return(nil) + + mci := new(MockClientIdentity) + owner64 := base64.StdEncoding.EncodeToString([]byte(owner)) + operator64 := base64.StdEncoding.EncodeToString([]byte(owner)) + + mci.On("GetID").Return(owner64, nil) + mci.On("GetID").Return(operator64, nil) + mci.On("GetMSPID").Return("Org1MSP", nil) + + mc := new(MockContext) + mc.On("GetStub").Return(ms) + mc.On("GetClientIdentity").Return(mci) + return mc, ms +} + +func TestBalanceOf(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + balance := c.BalanceOf(ctx, owner) + assert.Equal(t, 0, balance) + +} +func TestTotalSupply(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + totalNft := c.TotalSupply(ctx) + assert.Equal(t, 0, totalNft) + +} + +func TestOwnerOf(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + owner, _ := c.OwnerOf(ctx, "101") + assert.Equal(t, owner, owner) + +} + +func TestApprove(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + approved, _ := c.Approve(ctx, "", "101") + assert.Equal(t, true, approved) + +} + +func TestSetApprovalForAll(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + appAll, _ := c.SetApprovalForAll(ctx, operator, true) + assert.Equal(t, true, appAll) + +} + +func TestIsApprovedForAll(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + isApp, _ := c.SetApprovalForAll(ctx, operator, true) + assert.Equal(t, true, isApp) + +} + +func TestGetApproved(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + getApp, _ := c.GetApproved(ctx, "101") + assert.Equal(t, ""+operator+"", getApp) +} + +func TestTransferFrom(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + transfer, _ := c.TransferFrom(ctx, owner, operator, "101") + + assert.Equal(t, true, transfer) +} + +func TestName(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + name, _ := c.Name(ctx) + + assert.Equal(t, "lala", name) +} + +func TestSymbol(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + symbol, _ := c.Symbol(ctx) + + assert.Equal(t, "lelo", symbol) +} + +func TestTokenURI(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + tokenURI, _ := c.TokenURI(ctx, "101") + + assert.Equal(t, "https://example.com/nft101.json", tokenURI) +} + +func TestSetOption(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + option, _ := c.SetOption(ctx, "someName", "someSymbol") + assert.Equal(t, true, option) +} + +func TestMintWithTokenURI(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + mint, _ := c.MintWithTokenURI(ctx, "102", "https://example.com/nft102.json") + + nft := new(Nft) + nft.Owner = owner + nft.TokenId = "102" + nft.TokenURI = "https://example.com/nft102.json" + + assert.Equal(t, nft.Owner, mint.Owner) + assert.Equal(t, nft, mint) + +} + +func TestBurn(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + + burn, _ := c.Burn(ctx, "101") + assert.Equal(t, true, burn) +} + +func TestClientAccoundId(t *testing.T) { + ctx, _ := setupStub() + c := new(TokenERC721Contract) + client, _ := c.ClientAccountID(ctx) + assert.Equal(t, owner, client) +} diff --git a/token-erc-721/chaincode-go/chaincode/erc721.go b/token-erc-721/chaincode-go/chaincode/erc721.go new file mode 100644 index 00000000..5302f4d5 --- /dev/null +++ b/token-erc-721/chaincode-go/chaincode/erc721.go @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package chaincode + +// Define structs to be used by chaincode +type Nft struct { + TokenId string `json:"tokenId"` + Owner string `json:"owner"` + TokenURI string `json:"tokenURI"` + Approved string `json:"approved"` +} + +type Approval struct { + Owner string `json:"owner"` + Operator string `json:"operator"` + Approved bool `json:"approved"` +} + +type Transfer struct { + From string `json:"from"` + To string `json:"to"` + TokenId string `json:"tokenId"` +} diff --git a/token-erc-721/chaincode-go/go.mod b/token-erc-721/chaincode-go/go.mod new file mode 100644 index 00000000..808e8643 --- /dev/null +++ b/token-erc-721/chaincode-go/go.mod @@ -0,0 +1,39 @@ +module github.com/hyperledger/fabric-samples/token-erc-721/chaincode-go + +go 1.17 + +require ( + github.com/hyperledger/fabric-chaincode-go v0.0.0-20220131132609-1476cf1d3206 + github.com/hyperledger/fabric-contract-api-go v1.1.1 + github.com/hyperledger/fabric-protos-go v0.0.0-20220202165055-956c75de7b17 + github.com/stretchr/testify v1.7.0 +) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-openapi/jsonpointer v0.19.3 // indirect + github.com/go-openapi/jsonreference v0.19.2 // indirect + github.com/go-openapi/spec v0.19.4 // indirect + github.com/go-openapi/swag v0.19.5 // indirect + github.com/gobuffalo/envy v1.7.0 // indirect + github.com/gobuffalo/packd v0.3.0 // indirect + github.com/gobuffalo/packr v1.30.1 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/joho/godotenv v1.3.0 // indirect + github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.3.0 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect + golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 // indirect + golang.org/x/text v0.3.2 // indirect + google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect + google.golang.org/grpc v1.23.0 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/token-erc-721/chaincode-go/go.sum b/token-erc-721/chaincode-go/go.sum new file mode 100644 index 00000000..68e78ff4 --- /dev/null +++ b/token-erc-721/chaincode-go/go.sum @@ -0,0 +1,151 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A= +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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20220131132609-1476cf1d3206 h1:WAERjn+5lTfT8hVw5ik4uozvtei2F836AcRU+5fipmI= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20220131132609-1476cf1d3206/go.mod h1:poNJVTYwPIuHWJH0gyprZZSx70GpdWM2se3u/DEldYc= +github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0= +github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20220202165055-956c75de7b17 h1:TJzBJlpsO2wjlq/YyoAVLJCnb9MjeddYW2BHtQmbAI4= +github.com/hyperledger/fabric-protos-go v0.0.0-20220202165055-956c75de7b17/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +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/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +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/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/token-erc-721/chaincode-go/main.go b/token-erc-721/chaincode-go/main.go new file mode 100644 index 00000000..93d03610 --- /dev/null +++ b/token-erc-721/chaincode-go/main.go @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package main + +import ( + "github.com/hyperledger/fabric-contract-api-go/contractapi" + "github.com/hyperledger/fabric-contract-api-go/metadata" + "github.com/hyperledger/fabric-samples/token-erc-721/chaincode-go/chaincode" +) + +func main() { + nftContract := new(chaincode.TokenERC721Contract) + nftContract.Info.Version = "0.0.1" + nftContract.Info.Description = "ERC-721 fabric port" + nftContract.Info.License = new(metadata.LicenseMetadata) + nftContract.Info.License.Name = "Apache-2.0" + nftContract.Info.Contact = new(metadata.ContactMetadata) + nftContract.Info.Contact.Name = "Matias Salimbene" + + chaincode, err := contractapi.NewChaincode(nftContract) + chaincode.Info.Title = "ERC-721 chaincode" + chaincode.Info.Version = "0.0.1" + + if err != nil { + panic("Could not create chaincode from TokenERC721Contract." + err.Error()) + } + + err = chaincode.Start() + + if err != nil { + panic("Failed to start chaincode. " + err.Error()) + } +}