ERC20 token review error fix

Signed-off-by: renjithkn@gmail.com <renjithkn@gmail.com>
This commit is contained in:
renjithkn@gmail.com 2022-01-31 13:28:28 +00:00
parent 590851471b
commit efe232f5a5
29 changed files with 2140 additions and 822 deletions

View file

@ -0,0 +1,43 @@
/*
* 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 java plugin to add support for Java
id 'java'
// 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.
mainClassName = 'application.java.App'
}
// task for running the app after building dependencies
task runApp(type: Exec) {
dependsOn build
group = "Execution"
description = "Run the main class with ExecTask"
commandLine "java", "-classpath", sourceSets.main.runtimeClasspath.getAsPath(), javaMainClass
}

View file

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

View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or 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 UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$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 "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,104 @@
@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 init
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 init
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
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
: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 %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

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

View file

@ -0,0 +1,286 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// Running TestApp:
// gradle runApp
package application.java;
import java.io.File;
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.concurrent.TimeUnit;
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.SubmitException;
import org.hyperledger.fabric.client.SubmittedTransaction;
import org.hyperledger.fabric.client.Status;
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;
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;
public class App {
private static final String mspID = "Org1MSP";
private static final String channelName = "mychannel";
private static final String chaincodeName = "basic";
public static String assetId = "asset" + Instant.now().toEpochMilli();
// 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 keyPath = 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.
public static String peerEndpoint = "localhost:7051";
public static String overrideAuth = "peer0.org1.example.com";
public static void main(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()) {
// 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 contract = network.getContract(chaincodeName);
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger'
// function.
initLedger(contract);
// Return all the current assets on the ledger.
getAllAssets(contract);
// Create a new asset on the ledger.
createAsset(contract);
// Update an existing asset asynchronously.
transferAssetAsync(contract);
// Get the asset details by assetID.
readAssetById(contract);
// Update an asset which does not exist.
updateNonExistentAsset(contract);
} 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 {
File dir = new File(keyPath.toString());
File[] listOfFiles = dir.listFiles();
Path path = Paths.get(listOfFiles[0].getPath());
Reader keyReader = Files.newBufferedReader(path);
PrivateKey privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static String prettyJson(byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private static String prettyJson(String json) {
JsonElement parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
/**
* 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 static void initLedger(Contract contract) throws GatewayException, 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 static void getAllAssets(Contract contract) 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));
}
/**
* Submit a transaction synchronously, blocking until it has been committed to
* the ledger.
*/
private static void createAsset(Contract contract) throws GatewayException, 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 static void transferAssetAsync(Contract contract) throws GatewayException {
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 static void readAssetById(Contract contract) 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 static void updateNonExistentAsset(Contract contract) {
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);
if (!e.getDetails().isEmpty()) {
System.out.println("\n" + "Error Details: ");
for (ErrorDetail detail : e.getDetails()) {
System.out.println("address: " + detail.getAddress() + ", mspId: " + detail.getMspId()
+ ", message: " + detail.getMessage());
}
}
System.out.println("Transaction ID: " + e.getTransactionId());
} catch (CommitException e) {
System.out.println("*** Successfully caught the error: " + e);
e.printStackTrace(System.out);
System.out.println("Transaction ID: " + e.getTransactionId() + " status code: " + e.getCode());
}
}
}

View file

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

View file

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

View file

@ -1,90 +1,71 @@
# 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 .
```
## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down

View file

@ -0,0 +1,169 @@
/*
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.Printf("\n*** Start chaincode event listening\n")
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.Printf("\n*** CreateAsset committed successfully\n")
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.Printf("\n*** UpdateAsset committed successfully\n")
}
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.Printf("\n*** TransferAsset committed successfully\n")
}
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.Printf("\n*** DeleteAsset committed successfully\n")
}
func replayChaincodeEvents(ctx context.Context, network *client.Network, startBlock uint64) {
fmt.Printf("\n*** Start chaincode event replay\n")
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" {
return
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@
// 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -41,6 +41,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"
gradle run
popd
stopNetwork
# Run Javascript application
createNetwork
print "Initializing Javascript application"

View file

@ -34,3 +34,12 @@ popd
stopNetwork
print "Remove wallet storage"
rm -R ../asset-transfer-events/application-javascript/wallet
# Run Go gateway application
createNetwork
print "Initializing Go gateway application"
pushd ../asset-transfer-events/application-gateway-go
print "Executing application"
go run .
popd
stopNetwork

View file

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

View file

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

View file

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

View file

@ -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,7 +12,7 @@ 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
@ -20,7 +20,7 @@ The `test-network` and some of the chaincodes have been updated to support runni
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
@ -50,7 +50,7 @@ This sequence can be run as follows
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)
@ -62,6 +62,7 @@ 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_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export FABRIC_CFG_PATH=${PWD}/../config
# invoke the function
peer chaincode query -C mychannel -n basicts -c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' | jq
@ -78,7 +79,7 @@ To run the Java example, change the `deployCCAAS` command as follows, This will
### 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
@ -103,9 +104,9 @@ 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`
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`
### Golang
@ -131,7 +132,7 @@ Not starting docker containers; these are the commands we would have run
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.
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 here.
To manually build the docker image for the `asset-transfer-basic/chaincode-java`
@ -164,7 +165,7 @@ For all languages please note:
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.
@ -199,6 +200,6 @@ 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.

View file

@ -1,10 +1,8 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.example;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.*;
@ -16,504 +14,451 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import org.json.JSONObject;
import com.google.common.base.Strings;
@Contract(name = "TokenERC20Contract", info = @Info(title = "TokenERC20Contract", description = "A java chaincode for erc20 token", version = "0.0.1-SNAPSHOT"))
@Default
public final class TokenERC20Contract implements ContractInterface {
private static final String BALANCE_PREFIX = "balance";
private static final String ALLOWANCE_PREFIX = "allowance";
private static final String NAME_KEY = "name";
private static final String SYMBOL_KEY = "symbol";
private static final String DECIMALS_KEY = "decimals";
private static final String TOTAL_SUPPLY_KEY = "totalSupply";
private static final String TRANSFER_EVENT = "Transfer";
private static final String FROM = "from";
private static final String TO = "to";
private static final String VALUE= "value";
private static final String ERC20_OWNER_MSPID= "Org1MSP";
final private String balancePrefix = "balance";
final private String allowancePrefix = "allowance";
final private String nameKey = "name";
final private String symbolKey = "symbol";
final private String decimalsKey = "decimals";
final private String totalSupplyKey = "totalSupply";
/**
* Return the name of the token - e.g. "MyToken". The original function name is
* `name` in ERC20 specification. However, 'name' conflicts with a parameter
* `name` in `Contract` class. As a work around, we use `TokenName` as an
* alternative function name.
*
* @param {Context} ctx the transaction context
* @returns {String} Returns the name of the token
*/
@Transaction()
public String tokenName(final Context ctx) {
/**
* @Desc Return the name of the token - e.g. "MyToken". The original function name is `name` in
* ERC20 specification. However, 'name' conflicts with a parameter `name` in `Contract` class. As
* a work around, we use `TokenName` as an alternative function name.
* @param ctx the transaction context
* @returns Returns the name of the token
*/
ChaincodeStub stub = ctx.getStub();
String tokenName = stub.getStringState(nameKey);
if (tokenName.isEmpty()) {
@Transaction()
public String tokenName(final Context ctx) {
throw new ChaincodeException("Sorry ! Token name not found");
}
String tokenName = ctx.getStub().getStringState(NAME_KEY);
return tokenName;
if (Strings.isNullOrEmpty(tokenName)) {
throw new ChaincodeException("Sorry ! Token name not found.");
}
return tokenName;
}
/**
* @Desc Return the symbol of the token. E.g. âœHIXâ??.
* @param ctx the transaction context
* @returns Returns the symbol of the token
*/
@Transaction()
public String tokenSymbol(final Context ctx) {
String tokenSymbol = ctx.getStub().getStringState(SYMBOL_KEY);
if (Strings.isNullOrEmpty(tokenSymbol)) {
throw new ChaincodeException("Sorry ! Token symbol not found.");
}
return tokenSymbol;
}
/**
* @Desc Return the number of decimals the token uses e.g. 8, means to divide the token amount by
* 100000000 to get its user representation.
* @param ctx the transaction context
* @returns Returns the number of decimals
*/
@Transaction()
public int decimals(final Context ctx) {
String decimals = ctx.getStub().getStringState(DECIMALS_KEY);
if (Strings.isNullOrEmpty(decimals)) {
throw new ChaincodeException("Sorry ! Decimal not found.");
}
return Integer.parseInt(decimals);
}
/**
* @Desc Return the total token supply.
* @param ctx the transaction context
* @returns Returns the total token supply
*/
@Transaction()
public long totalSupply(final Context ctx) {
String totalSupply = ctx.getStub().getStringState(TOTAL_SUPPLY_KEY);
if (Strings.isNullOrEmpty(totalSupply)) {
throw new ChaincodeException("Sorry ! Total Supply not found.");
}
return Long.parseLong(totalSupply);
}
/**
* BalanceOf returns the balance of the given account.
*
* @param ctx the transaction context
* @param owner The owner from which the balance will be retrieved
* @returns Returns the account balance
*/
@Transaction()
public long balanceOf(final Context ctx, final String owner) {
ChaincodeStub stub = ctx.getStub();
CompositeKey balanceKey = ctx.getStub().createCompositeKey(BALANCE_PREFIX, owner);
String balance = stub.getStringState(balanceKey.toString());
if (Strings.isNullOrEmpty(balance)) {
String errorMessage = String.format("Balance of the owner %s not exists", owner);
throw new ChaincodeException(errorMessage);
}
return Long.parseLong(balance);
}
/**
* @Desc Transfer transfers tokens from client account to recipient account. recipient account
* must be a valid clientID as returned by the ClientAccountID() function.
*
* @param ctx the transaction context
* @param to The recipient
* @param value The amount of token to be transferred
* @returns Return whether the transfer was successful or not
*/
@Transaction()
public void transfer(final Context ctx, final String to, long _value) {
String from = ctx.getClientIdentity().getId();
this.doTransfer(ctx, from, to, _value);
ctx.getStub().setEvent(TRANSFER_EVENT, new JSONObject().put(FROM, from).put(TO, to)
.put(VALUE, _value).toString().getBytes(UTF_8));
}
/**
* Transfer `value` amount of tokens from `from` to `to`.
*
* @param ctx the transaction context
* @param from The sender
* @param to The recipient
* @param value The amount of token to be transferred
* @returns Return whether the transfer was successful or not
*/
@Transaction()
public void transferFrom(Context ctx, final String from, final String to, String _value) {
String spender = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
// Retrieve the allowance of the spender
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX, from, spender);
String currentAllowanceStr = stub.getStringState(allowanceKey.toString());
if (Strings.isNullOrEmpty(currentAllowanceStr)) {
String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
throw new ChaincodeException(errorMessage);
}
long currentAllowance = Long.parseLong(currentAllowanceStr);
// Convert value from string to int
long valueInt = Long.parseLong(_value);
// Check if the transferred value is less than the allowance
if (currentAllowance < valueInt) {
String errorMessage = String.format("The spender does not have enough allowance to spend.");
throw new ChaincodeException(errorMessage);
}
/**
* Return the symbol of the token. E.g. HIX.
*
* @param {Context} ctx the transaction context
* @returns {String} Returns the symbol of the token
*/
@Transaction()
public String tokenSymbol(final Context ctx) {
ChaincodeStub stub = ctx.getStub();
String tokenSymbol = stub.getStringState(symbolKey);
if (tokenSymbol.isEmpty()) {
this.doTransfer(ctx, from, to, valueInt);
throw new ChaincodeException("Sorry ! Token symbol not found");
}
// Decrease the allowance
long updatedAllowance = currentAllowance - valueInt;
stub.putStringState(allowanceKey.toString(), String.valueOf(updatedAllowance));
stub.setEvent(TRANSFER_EVENT, new JSONObject().put(FROM, from).put(TO, to)
.put(VALUE, valueInt).toString().getBytes(UTF_8));
return tokenSymbol;
}
private void doTransfer(final Context ctx, final String _from, final String _to, long _value) {
if (_from.equalsIgnoreCase(_to)) {
throw new ChaincodeException("cannot transfer to and from same client account");
}
if (_value < 0) { // transfer of 0 is allowed in ERC20, so just validate against negative
// amounts
throw new ChaincodeException("transfer amount cannot be negative");
}
ChaincodeStub stub = ctx.getStub();
// Retrieve the current balance of the sender
CompositeKey fromBalanceKey = stub.createCompositeKey(BALANCE_PREFIX, _from);
String fromCurrentBalance = stub.getStringState(fromBalanceKey.toString());
if (Strings.isNullOrEmpty(fromCurrentBalance)) {
String errorMessage = String.format("client account %s has no balance", _from);
throw new ChaincodeException(errorMessage);
}
/**
* Return the number of decimals the token uses e.g. 8, means to divide the
* token amount by 100000000 to get its user representation.
*
* @param {Context} ctx the transaction context
* @returns {Number} Returns the number of decimals
*/
@Transaction()
public Integer decimals(final Context ctx) {
long _fromCurrentBalance = Long.parseLong(fromCurrentBalance.toString());
ChaincodeStub stub = ctx.getStub();
String decimals = stub.getStringState(decimalsKey);
if (decimals.isEmpty()) {
throw new ChaincodeException("Sorry ! Decimal not found");
}
return Integer.parseInt(decimals);
// Check if the sender has enough tokens to spend.
if (_fromCurrentBalance < _value) {
String errorMessage = String.format("client account %s has insufficient funds", _from);
throw new ChaincodeException(errorMessage);
}
/**
* Return the total token supply.
*
* @param {Context} ctx the transaction context
* @returns {Number} Returns the total token supply
*/
@Transaction()
public Long totalSupply(final Context ctx) {
ChaincodeStub stub = ctx.getStub();
String totalSupply = stub.getStringState(totalSupplyKey);
if (totalSupply.isEmpty()) {
// Retrieve the current balance of the recepient
CompositeKey toBalanceKey = stub.createCompositeKey(BALANCE_PREFIX, _to);
String toCurrentBalance = stub.getStringState(toBalanceKey.toString());
throw new ChaincodeException("Sorry ! Total Supply not found");
}
return Long.parseLong(totalSupply);
long _toCurrentBalance = 0;
// If recipient current balance doesn't yet exist, we'll create it with a
// current balance of 0
if (Strings.isNullOrEmpty(toCurrentBalance)) {
_toCurrentBalance = 0;
} else {
_toCurrentBalance = Long.parseLong(toCurrentBalance.trim());
}
/**
* BalanceOf returns the balance of the given account.
*
* @param {Context} ctx the transaction context
* @param {String} owner The owner from which the balance will be retrieved
* @returns {Number} Returns the account balance
*/
@Transaction()
public long balanceOf(final Context ctx, final String owner) {
// Update the balance
long fromUpdatedBalance = _fromCurrentBalance - _value;
long toUpdatedBalance = _toCurrentBalance + _value;
ChaincodeStub stub = ctx.getStub();
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, owner);
stub.putStringState(fromBalanceKey.toString(), String.valueOf(fromUpdatedBalance));
String balance = stub.getStringState(balanceKey.toString().trim());
if (balance == null || balance.isEmpty() || balance.length() == 0) {
String errorMessage = String.format("Balance of the owner %s not exists", owner);
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage);
}
stub.putStringState(toBalanceKey.toString(), String.valueOf(toUpdatedBalance));
return Long.parseLong(balance.toString());
}
/**
* @Desc Allows `spender` to spend `value` amount of tokens from the owner.
*
* @param ctx the transaction context
* @param spender The spender
* @param value The amount of tokens to be approved for transfer
* @returns Return whether the approval was successful or not
*/
@Transaction()
public void approve(final Context ctx, final String spender, final String value) {
String owner = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX, owner, spender);
long valueInt = Long.parseLong(value);
stub.putStringState(allowanceKey.toString(), String.valueOf(valueInt));
stub.setEvent("Approval", new JSONObject().put("owner", owner).put("spender", spender)
.put(VALUE, valueInt).toString().getBytes(UTF_8));
}
/**
* @Desc Returns the amount of tokens which `spender` is allowed to withdraw from `owner`.
*
* @param ctx the transaction context
* @param owner The owner of tokens
* @param spender The spender who are able to transfer the tokens
* @returns Return the amount of remaining tokens allowed to spent
*/
@Transaction()
public long allowance(final Context ctx, final String owner, final String spender) {
ChaincodeStub stub = ctx.getStub();
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX, owner, spender);
String allowanceBytes = stub.getStringState(allowanceKey.toString());
if (Strings.isNullOrEmpty(allowanceBytes)) {
String errorMessage = String.format("spender account %s has no allowance from", spender,
owner);
throw new ChaincodeException(errorMessage);
}
/**
* Transfer transfers tokens from client account to recipient account. recipient
* account must be a valid clientID as returned by the ClientAccountID()
* function.
*
* @param {Context} ctx the transaction context
* @param {String} to The recipient
* @param {Integer} value The amount of token to be transferred
* @returns {Boolean} Return whether the transfer was successful or not
*/
@Transaction()
public boolean transfer(final Context ctx, final String to, String _value) {
long allowance = Long.parseLong(allowanceBytes);
return allowance;
}
String from = ctx.getClientIdentity().getId();
long value = Long.parseLong(_value.trim());
boolean transferResp = this.doTransfer(ctx, from, to, value);
/**
* @Desc Set optional information for a token.
*
* @param ctx the transaction context
* @param name The name of the token
* @param symbol The symbol of the token
* @param decimals The decimals of the token
* @param totalSupply The totalSupply of the token
*/
@Transaction()
public void setOptions(final Context ctx, final String name, final String symbol,
final String decimals) {
ChaincodeStub stub = ctx.getStub();
stub.putStringState(NAME_KEY, name);
stub.putStringState(SYMBOL_KEY, symbol);
stub.putStringState(DECIMALS_KEY, decimals);
if (!transferResp) {
String errorMessage = String.format("Cannot transfer to and from same client account");
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage);
}
}
ChaincodeStub stub = ctx.getStub();
JSONObject obj = new JSONObject();
obj.put("from", from);
obj.put("to", to);
obj.put("value", value);
stub.setEvent("Transfer", this.serialize(obj));
return true;
/**
* Mint creates new tokens and adds them to minter's account balance
*
* @param ctx the transaction context
* @param amount amount of tokens to be minted
* @returns The balance
*/
@Transaction()
public void mint(final Context ctx, final String amount) {
// Check minter authorization - this sample assumes Org1 is the central banker
// with privilege to mint new tokens
String clientMSPID = ctx.getClientIdentity().getMSPID();
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase(ERC20_OWNER_MSPID)) {
throw new ChaincodeException("Client is not authorized to mint new tokens");
}
/**
* Transfer `value` amount of tokens from `from` to `to`.
*
* @param {Context} ctx the transaction context
* @param {String} from The sender
* @param {String} to The recipient
* @param {Integer} value The amount of token to be transferred
* @returns {Boolean} Return whether the transfer was successful or not
*/
@Transaction()
public boolean transferFrom(Context ctx, final String from, final String to, String _value) {
String spender = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
// Retrieve the allowance of the spender
CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, from, spender);
String currentAllowanceStr = stub.getStringState(allowanceKey.toString().trim());
if (currentAllowanceStr.isBlank() || currentAllowanceStr.length() == 0) {
String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage);
}
long currentAllowance = Long.parseLong(currentAllowanceStr.toString());
// Convert value from string to int
Long valueInt = Long.parseLong(_value);
// Check if the transferred value is less than the allowance
if (currentAllowance < valueInt) {
String errorMessage = String.format("The spender does not have enough allowance to spend.");
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage);
}
boolean transferResp = this.doTransfer(ctx, from, to, valueInt);
if (!transferResp) {
throw new ChaincodeException("Failed to transfer");
}
// Decrease the allowance
long updatedAllowance = currentAllowance - valueInt;
stub.putStringState(allowanceKey.toString().trim(), String.valueOf(updatedAllowance));
System.out.printf("spender %s allowance updated from %d to %d", spender, currentAllowance, updatedAllowance);
JSONObject obj = new JSONObject();
obj.put("from", from);
obj.put("to", to);
obj.put("value", valueInt);
stub.setEvent("Transfer", this.serialize(obj));
System.out.println("transferFrom ended successfully");
return true;
// Get ID of submitting client identity
String minter = ctx.getClientIdentity().getId();
long amountInt = Long.parseLong(amount.trim());
if (amountInt <= 0) {
throw new ChaincodeException("Mint amount must be a positive integer");
}
@Transaction()
private boolean doTransfer(final Context ctx, final String _from, final String _to, long _value) {
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX, minter);
if (_from.equalsIgnoreCase(_to)) {
throw new ChaincodeException("cannot transfer to and from same client account");
}
String currentBalanceBytes = stub.getStringState(balanceKey.toString());
// If minter current balance doesn't yet exist, we'll create it with a current
// balance of 0
long currentBalance = 0;
if (_value < 0) { // transfer of 0 is allowed in ERC20, so just validate against negative amounts
throw new ChaincodeException("transfer amount cannot be negative");
}
if (Strings.isNullOrEmpty(currentBalanceBytes)) {
ChaincodeStub stub = ctx.getStub();
// Retrieve the current balance of the sender
CompositeKey fromBalanceKey = stub.createCompositeKey(balancePrefix, _from.trim());
String fromCurrentBalance = stub.getStringState(fromBalanceKey.toString().trim());
if (fromCurrentBalance.isBlank() || fromCurrentBalance.length() == 0) {
String errorMessage = String.format("client account %s has no balance", _from);
throw new ChaincodeException(errorMessage);
currentBalance = 0;
}
} else {
long _fromCurrentBalance = Long.parseLong(fromCurrentBalance.toString().trim());
// Check if the sender has enough tokens to spend.
if (_fromCurrentBalance < _value) {
String errorMessage = String.format("client account %s has insufficient funds", _from);
throw new ChaincodeException(errorMessage);
}
// Retrieve the current balance of the recepient
CompositeKey toBalanceKey = stub.createCompositeKey(balancePrefix, _to);
String toCurrentBalance = stub.getStringState(toBalanceKey.toString().trim());
long _toCurrentBalance = 0;
// If recipient current balance doesn't yet exist, we'll create it with a
// current balance of 0
if (toCurrentBalance.isBlank() || toCurrentBalance.length() == 0) {
_toCurrentBalance = 0;
} else {
_toCurrentBalance = Long.parseLong(toCurrentBalance.trim());
}
// Update the balance
long fromUpdatedBalance = _fromCurrentBalance - _value;
long toUpdatedBalance = _toCurrentBalance + _value;
stub.putStringState(fromBalanceKey.toString().trim(), String.valueOf(fromUpdatedBalance));
stub.putStringState(toBalanceKey.toString().trim(), String.valueOf(toUpdatedBalance));
System.out.printf("client %s balance updated from %d to %d", _from, _fromCurrentBalance, fromUpdatedBalance);
System.out.printf("recipient %s balance updated from %d to %d", _to, _toCurrentBalance, toUpdatedBalance);
return true;
}
/**
* Allows `spender` to spend `value` amount of tokens from the owner.
*
* @param {Context} ctx the transaction context
* @param {String} spender The spender
* @param {Integer} value The amount of tokens to be approved for transfer
* @returns {Boolean} Return whether the approval was successful or not
*/
@Transaction()
public boolean approve(final Context ctx, final String spender, final String value) {
String owner = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, owner, spender);
long valueInt = Long.parseLong(value);
stub.putStringState(allowanceKey.toString().trim(), String.valueOf(valueInt));
JSONObject obj = new JSONObject();
obj.put("owner", owner);
obj.put("spender", spender);
obj.put("value", valueInt);
stub.setEvent("Approval", this.serialize(obj));
System.out.println("Approve ended successfully");
return true;
currentBalance = Long.parseLong(currentBalanceBytes);
}
long updatedBalance = currentBalance + amountInt;
/**
* Returns the amount of tokens which `spender` is allowed to withdraw from
* `owner`.
*
* @param {Context} ctx the transaction context
* @param {String} owner The owner of tokens
* @param {String} spender The spender who are able to transfer the tokens
* @returns {Number} Return the amount of remaining tokens allowed to spent
*/
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
@Transaction()
public long allowance(final Context ctx, final String owner, final String spender) {
// Increase totalSupply
String totalSupplyBytes = stub.getStringState(TOTAL_SUPPLY_KEY);
long totalSupply = 0;
if (Strings.isNullOrEmpty(totalSupplyBytes)) {
ChaincodeStub stub = ctx.getStub();
totalSupply = 0;
CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, owner, spender);
String allowanceBytes = stub.getStringState(allowanceKey.toString().trim());
} else {
if (allowanceBytes.isBlank() || allowanceBytes.length() == 0) {
String errorMessage = String.format("spender account %s has no allowance from", spender, owner);
throw new ChaincodeException(errorMessage);
}
long allowance = Long.parseLong(allowanceBytes.toString().trim());
return allowance;
totalSupply = Long.parseLong(totalSupplyBytes.toString());
}
/**
* Set optional infomation for a token.
*
* @param {Context} ctx the transaction context
* @param {String} name The name of the token
* @param {String} symbol The symbol of the token
* @param {String} decimals The decimals of the token
* @param {String} totalSupply The totalSupply of the token
*/
@Transaction()
public boolean setOptions(final Context ctx, final String name, final String symbol, final String decimals) {
ChaincodeStub stub = ctx.getStub();
stub.putStringState(nameKey, name);
stub.putStringState(symbolKey, symbol);
stub.putStringState(decimalsKey, decimals);
totalSupply = totalSupply + amountInt;
stub.putStringState(TOTAL_SUPPLY_KEY, String.valueOf(totalSupply));
stub.setEvent(TRANSFER_EVENT, new JSONObject().put(FROM, "0x0").put(TO, minter)
.put(VALUE, amountInt).toString().getBytes(UTF_8));
System.out.printf("name:%s, symbol: %s, decimals: %s", name, symbol, decimals);
return true;
}
/**
* @Desc Burn redeem tokens from minter's account balance.
* @param ctx the transaction context
* @param amount amount of tokens to be burned
* @returns The balance
*/
@Transaction()
public void burn(final Context ctx, final String amount) {
// Check minter authorization - this sample assumes Org1 is the central banker
// with privilege to burn tokens
String clientMSPID = ctx.getClientIdentity().getMSPID();
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase(ERC20_OWNER_MSPID)) {
throw new ChaincodeException("client is not authorized to mint new tokens");
}
/**
* Mint creates new tokens and adds them to minter's account balance
*
* @param {Context} ctx the transaction context
* @param {Integer} amount amount of tokens to be minted
* @returns {Object} The balance
*/
@Transaction()
public boolean mint(final Context ctx, final String amount) {
String minter = ctx.getClientIdentity().getId();
// Check minter authorization - this sample assumes Org1 is the central banker
// with privilege to mint new tokens
long amountInt = Long.parseLong(amount);
String clientMSPID = ctx.getClientIdentity().getMSPID();
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase("Org1MSP")) {
throw new ChaincodeException("client is not authorized to mint new tokens");
}
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX, minter);
// Get ID of submitting client identity
String minter = ctx.getClientIdentity().getId();
long amountInt = Long.parseLong(amount.trim());
if (amountInt <= 0) {
throw new ChaincodeException("mint amount must be a positive integer");
}
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
String currentBalanceBytes = stub.getStringState(balanceKey.toString().trim());
// If minter current balance doesn't yet exist, we'll create it with a current
// balance of 0
long currentBalance = 0;
if (currentBalanceBytes.isBlank() || currentBalanceBytes.length() == 0) {
currentBalance = 0;
} else {
currentBalance = Long.parseLong(currentBalanceBytes.toString());
}
long updatedBalance = currentBalance + amountInt;
stub.putStringState(balanceKey.toString().trim(), String.valueOf(updatedBalance));
// Increase totalSupply
String totalSupplyBytes = stub.getStringState(totalSupplyKey.trim());
long totalSupply = 0;
if (totalSupplyBytes.isBlank() || totalSupplyBytes.length() == 0) {
System.out.println("Initialize the tokenSupply");
totalSupply = 0;
} else {
totalSupply = Long.parseLong(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt;
stub.putStringState(totalSupplyKey.trim(), String.valueOf(totalSupply));
JSONObject obj = new JSONObject();
obj.put("from", "0x0");
obj.put("to", minter);
obj.put("value", amountInt);
stub.setEvent("Transfer", this.serialize(obj));
// System.out.printf("minter account %s balance updated from %d to %d",minter,
// currentBalance ,updatedBalance);
return true;
String currentBalanceBytes = stub.getStringState(balanceKey.toString());
if (Strings.isNullOrEmpty(currentBalanceBytes)) {
throw new ChaincodeException("The balance does not exist");
}
long currentBalance = Long.valueOf(currentBalanceBytes);
long updatedBalance = currentBalance - amountInt;
/**
* Burn redeem tokens from minter's account balance
*
* @param {Context} ctx the transaction context
* @param {Integer} amount amount of tokens to be burned
* @returns {Object} The balance
*/
@Transaction()
public boolean burn(final Context ctx, final String amount) {
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
// Check minter authorization - this sample assumes Org1 is the central banker
// with privilege to burn tokens
String clientMSPID = ctx.getClientIdentity().getMSPID();
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase("Org1MSP")) {
throw new ChaincodeException("client is not authorized to mint new tokens");
}
String minter = ctx.getClientIdentity().getId();
long amountInt = Long.parseLong(amount);
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
String currentBalanceBytes = stub.getStringState(balanceKey.toString().trim());
if (currentBalanceBytes.isBlank() || currentBalanceBytes.length() == 0) {
throw new ChaincodeException("The balance does not exist");
}
long currentBalance = Long.valueOf(currentBalanceBytes.toString());
long updatedBalance = currentBalance - amountInt;
stub.putStringState(balanceKey.toString().trim(), String.valueOf(updatedBalance));
// Decrease totalSupply
String totalSupplyBytes = stub.getStringState(totalSupplyKey.toString().trim());
if (totalSupplyBytes.isBlank() || totalSupplyBytes.length() == 0) {
throw new ChaincodeException("totalSupply does not exist.");
}
long totalSupply = Long.parseLong(totalSupplyBytes.toString()) - amountInt;
stub.putStringState(totalSupplyKey.toString().trim(), String.valueOf(totalSupply));
// Emit the Transfer event
JSONObject obj = new JSONObject();
obj.put("from", minter);
obj.put("to", "0x0");
obj.put("value", amountInt);
stub.setEvent("Transfer", this.serialize(obj));
System.out.printf("minter account %s balance updated from %d to %d", minter, currentBalance, updatedBalance);
return true;
// Decrease totalSupply
String totalSupplyBytes = stub.getStringState(TOTAL_SUPPLY_KEY);
if (Strings.isNullOrEmpty(totalSupplyBytes)) {
throw new ChaincodeException("totalSupply does not exist.");
}
long totalSupply = Long.parseLong(totalSupplyBytes.toString()) - amountInt;
stub.putStringState(TOTAL_SUPPLY_KEY, String.valueOf(totalSupply));
/**
* ClientAccountBalance returns the balance of the requesting client's account.
*
* @param {Context} ctx the transaction context
* @returns {Number} Returns the account balance
*/
// Emit the Transfer event
@Transaction()
public long getClientAccountBalance(final Context ctx) {
// Get ID of submitting client identity
ChaincodeStub stub = ctx.getStub();
String clientAccountID = ctx.getClientIdentity().getId();
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, clientAccountID);
String balanceBytes = stub.getStringState(balanceKey.toString().trim());
if (balanceBytes.isBlank() || balanceBytes.length() == 0) {
stub.setEvent(TRANSFER_EVENT, new JSONObject().put(FROM, minter).put(TO, "0x0")
.put(VALUE, amountInt).toString().getBytes(UTF_8));
String errorMessage = String.format("the account %s does not exist", clientAccountID);
throw new ChaincodeException(errorMessage);
}
long balance = Long.parseLong(balanceBytes.trim());
}
return balance;
/**
* @Desc: ClientAccountBalance returns the balance of the requesting client's account.
*
* @param ctx the transaction context
* @returns Returns the account balance
*/
@Transaction()
public long getClientAccountBalance(final Context ctx) {
// Get ID of submitting client identity
ChaincodeStub stub = ctx.getStub();
String clientAccountID = ctx.getClientIdentity().getId();
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX, clientAccountID);
String balanceBytes = stub.getStringState(balanceKey.toString());
if (Strings.isNullOrEmpty(balanceBytes)) {
String errorMessage = String.format("the account %s does not exist", clientAccountID);
throw new ChaincodeException(errorMessage);
}
long balance = Long.parseLong(balanceBytes);
// 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
@Transaction()
public String getClientAccountID(final Context ctx) {
// Get ID of submitting client identity
String clientAccountID = ctx.getClientIdentity().getId();
return clientAccountID;
}
return balance;
}
private byte[] serialize(Object object) {
String jsonStr = new JSONObject(object).toString();
return jsonStr.getBytes(UTF_8);
}
/**
* @Desc: 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.
*
*/
@Transaction()
public String getClientAccountID(final Context ctx) {
// Get ID of submitting client identity
String clientAccountID = ctx.getClientIdentity().getId();
return clientAccountID;
}
}

View file

@ -4,7 +4,6 @@
*/
package org.example;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ClientIdentity;
@ -21,321 +20,309 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TokenERC20ContractTest {
final private String balancePrefix = "balance";
final private String balancePrefix = "balance";
final private String nameKey = "name";
final private String symbolKey = "symbol";
final private String decimalsKey = "decimals";
final private String totalSupplyKey = "totalSupply";
final private String nameKey = "name";
final private String symbolKey = "symbol";
final private String decimalsKey = "decimals";
final private String totalSupplyKey = "totalSupply";
@Nested
class InvokeQueryERC20TokenOPtionsTransaction {
@Nested
class InvokeQueryERC20TokenOPtionsTransaction {
@Test
public void whenTokenNameExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(nameKey)).thenReturn("ARBTToken");
@Test
public void whenTokenNameExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(nameKey)).thenReturn("ARBTToken");
String toknName = contract.tokenName(ctx);
String toknName = contract.tokenName(ctx);
assertThat(toknName).isEqualTo("ARBTToken");
}
@Test
public void whenTokenNameDoesNotExist() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(nameKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.tokenName(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Token name not found");
}
@Test
public void whenTokenSymbolExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(symbolKey)).thenReturn("ARBT");
String toknName = contract.tokenSymbol(ctx);
assertThat(toknName).isEqualTo("ARBT");
}
@Test
public void whenTokenSymbolDoesNotExist() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(symbolKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.tokenSymbol(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Token symbol not found");
}
@Test
public void whenTokenDecimalExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(decimalsKey)).thenReturn("18");
long decimal = contract.decimals(ctx);
assertThat(decimal).isEqualTo(18);
}
@Test
public void whenTokenDecimalNotExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(decimalsKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.decimals(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Decimal not found");
}
@Test
public void whenTokenTotalSupplyExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(totalSupplyKey)).thenReturn("222222222222");
long totalSupply = contract.totalSupply(ctx);
assertThat(totalSupply).isEqualTo(222222222222L);
}
@Test
public void whenTokenTotalSupplyNotExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(totalSupplyKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.totalSupply(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Total Supply not found");
}
@Test
public void whenClientAccountIDTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
assertThat(identity.getMSPID()).isEqualTo("Org1MSP");
when(ctx.getClientIdentity()).thenReturn(identity);
String id = contract.getClientAccountID(ctx);
String actualId = "x509::CN=admin, OU=Fabric, O=Hyperledger, ST=North Carolina, C=US::CN=example.com,"
+ " OU=WWW, O=Internet Widgets, L=San Francisco, ST=California, C=US";
assertThat(id).isEqualTo(actualId);
}
assertThat(toknName).isEqualTo("ARBTToken");
}
@Nested
class TokenOperationsInvoke {
@Test
public void whenTokenNameDoesNotExist() {
TokenERC20Contract contract = new TokenERC20Contract();
final Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(nameKey)).thenReturn("");
@Test
public void setOptionsTest() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
contract.setOptions(ctx, "ARBTToken", "ARBT", "18");
verify(stub).putStringState(nameKey, "ARBTToken");
verify(stub).putStringState(symbolKey, "ARBT");
verify(stub).putStringState(decimalsKey, "18");
}
Throwable thrown = catchThrowable(() -> {
contract.tokenName(ctx);
});
@Test
public void whenOrgMintTokensTest() throws Exception {
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Token name not found.");
}
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
@Test
public void whenTokenSymbolExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(symbolKey)).thenReturn("ARBT");
when(ctx.getStub()).thenReturn(stub);
boolean returnValue = contract.mint(ctx, "1000");
assertThat(returnValue).isEqualTo(true);
String totalSupply = stub.getStringState(totalSupplyKey.trim());
assertThat(totalSupply).isEqualTo("1000");
String minter = ctx.getClientIdentity().getId();
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
String updatedBalance = stub.getStringState(balanceKey.toString().trim());
assertThat(updatedBalance).isEqualTo("1000");
String toknName = contract.tokenSymbol(ctx);
}
@Test
public void whenUserTransferTokenTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
boolean returnValue = contract.mint(ctx, "1000");
String minter = ctx.getClientIdentity().getId();
String _to = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
boolean transferResult = contract.transfer(ctx, _to, "100");
CompositeKey toBalanceKey = stub.createCompositeKey(balancePrefix, _to);
String _toCurrentBalance = stub.getStringState(toBalanceKey.toString().trim());
Long totalSupply = contract.totalSupply(ctx);
Long fromBalance = contract.balanceOf(ctx, minter);
((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
Long _toBalance = contract.balanceOf(ctx, _to);
assertThat(transferResult).isEqualTo(true);
assertThat(returnValue).isEqualTo(true);
assertThat(totalSupply).isEqualTo(1000);
assertThat(_toCurrentBalance).isEqualTo("100");
assertThat(fromBalance).isEqualTo(900);
assertThat(_toBalance).isEqualTo(100);
}
@Test
public void whenOrgBurnsTokenTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
boolean returnValue = contract.mint(ctx, "1000");
String minter = ctx.getClientIdentity().getId();
boolean burnResult = contract.burn(ctx, "100");
Long totalSupply = contract.totalSupply(ctx);
Long fromBalance = contract.balanceOf(ctx, minter);
assertThat(returnValue).isEqualTo(true);
assertThat(burnResult).isEqualTo(true);
assertThat(totalSupply).isEqualTo(900);
assertThat(fromBalance).isEqualTo(900);
}
assertThat(toknName).isEqualTo("ARBT");
}
@Nested
class InvokeERC20AllowanceTransactions {
@Test
public void whenTokenSymbolDoesNotExist() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(symbolKey)).thenReturn("");
private Context ctx = null;
private ChaincodeStub stub = null;
private ClientIdentity identity = null;
private TokenERC20Contract contract = null;
Throwable thrown = catchThrowable(() -> {
contract.tokenSymbol(ctx);
});
@BeforeEach
public void initialize() {
try {
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Token symbol not found.");
}
this.ctx = mock(Context.class);
this.stub = new ChaincodeStubNaiveImpl();
this.identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract = new TokenERC20Contract();
contract.mint(ctx, "1000");
@Test
public void whenTokenDecimalExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(decimalsKey)).thenReturn("18");
} catch (Exception e) {
e.printStackTrace();
}
}
long decimal = contract.decimals(ctx);
@Test
public void approveForTokenAllowanceTest() {
String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
boolean result = contract.approve(ctx, spender, "200");
assertThat(result).isEqualTo(true);
String owner = ctx.getClientIdentity().getId();
long allowance = contract.allowance(ctx, owner, spender);
assertThat(allowance).isEqualTo(200);
}
@Test
public void allowanceTransferFromTest() throws Exception {
/*
* ChaincodeStub localStub = new ChaincodeStubNaiveImpl();
* ((ChaincodeStubNaiveImpl)
* localStub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS); Context
* localCtx = mock(Context.class); ClientIdentity localidentity = new
* ClientIdentity(localStub);
* when(localCtx.getClientIdentity()).thenReturn(localidentity);
* when(localCtx.getStub()).thenReturn(localStub);
*/
String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
String to = "x509::CN=User2@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
boolean result = contract.approve(ctx, spender, "200");
String owner = ctx.getClientIdentity().getId();
((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
boolean transferResult = contract.transferFrom(ctx, owner, to, "100");
long allowance = contract.allowance(ctx, owner, spender);
assertThat(result).isEqualTo(true);
assertThat(transferResult).isEqualTo(true);
assertThat(allowance).isEqualTo(100);
}
assertThat(decimal).isEqualTo(18);
}
@Test
public void whenTokenDecimalNotExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(decimalsKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.decimals(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Decimal not found.");
}
@Test
public void whenTokenTotalSupplyExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(totalSupplyKey)).thenReturn("222222222222");
long totalSupply = contract.totalSupply(ctx);
assertThat(totalSupply).isEqualTo(222222222222L);
}
@Test
public void whenTokenTotalSupplyNotExists() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(totalSupplyKey)).thenReturn("");
Throwable thrown = catchThrowable(() -> {
contract.totalSupply(ctx);
});
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
.hasMessage("Sorry ! Total Supply not found.");
}
@Test
public void whenClientAccountIDTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
assertThat(identity.getMSPID()).isEqualTo("Org1MSP");
when(ctx.getClientIdentity()).thenReturn(identity);
String id = contract.getClientAccountID(ctx);
String actualId = "x509::CN=admin, OU=Fabric, O=Hyperledger, ST=North Carolina, C=US::CN=example.com,"
+ " OU=WWW, O=Internet Widgets, L=San Francisco, ST=California, C=US";
assertThat(id).isEqualTo(actualId);
}
}
@Nested
class TokenOperationsInvoke {
@Test
public void setOptionsTest() {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
contract.setOptions(ctx, "ARBTToken", "ARBT", "18");
verify(stub).putStringState(nameKey, "ARBTToken");
verify(stub).putStringState(symbolKey, "ARBT");
verify(stub).putStringState(decimalsKey, "18");
}
@Test
public void whenOrgMintTokensTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract.mint(ctx, "1000");
String totalSupply = stub.getStringState(totalSupplyKey);
assertThat(totalSupply).isEqualTo("1000");
String minter = ctx.getClientIdentity().getId();
CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
String updatedBalance = stub.getStringState(balanceKey.toString());
assertThat(updatedBalance).isEqualTo("1000");
}
@Test
public void whenUserTransferTokenTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract.mint(ctx, "1000");
String minter = ctx.getClientIdentity().getId();
String _to = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
contract.transfer(ctx, _to, 100);
CompositeKey toBalanceKey = stub.createCompositeKey(balancePrefix, _to);
String _toCurrentBalance = stub.getStringState(toBalanceKey.toString());
Long totalSupply = contract.totalSupply(ctx);
Long fromBalance = contract.balanceOf(ctx, minter);
((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
Long _toBalance = contract.balanceOf(ctx, _to);
assertThat(totalSupply).isEqualTo(1000);
assertThat(_toCurrentBalance).isEqualTo("100");
assertThat(fromBalance).isEqualTo(900);
assertThat(_toBalance).isEqualTo(100);
}
@Test
public void whenOrgBurnsTokenTest() throws Exception {
TokenERC20Contract contract = new TokenERC20Contract();
Context ctx = mock(Context.class);
final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
final ClientIdentity identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract.mint(ctx, "1000");
String minter = ctx.getClientIdentity().getId();
contract.burn(ctx, "100");
Long totalSupply = contract.totalSupply(ctx);
Long fromBalance = contract.balanceOf(ctx, minter);
assertThat(totalSupply).isEqualTo(900);
assertThat(fromBalance).isEqualTo(900);
}
}
@Nested
class InvokeERC20AllowanceTransactions {
private Context ctx = null;
private ChaincodeStub stub = null;
private ClientIdentity identity = null;
private TokenERC20Contract contract = null;
@BeforeEach
public void initialize() {
try {
this.ctx = mock(Context.class);
this.stub = new ChaincodeStubNaiveImpl();
this.identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract = new TokenERC20Contract();
contract.mint(ctx, "1000");
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void approveForTokenAllowanceTest() {
String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
contract.approve(ctx, spender, "200");
String owner = ctx.getClientIdentity().getId();
long allowance = contract.allowance(ctx, owner, spender);
assertThat(allowance).isEqualTo(200);
}
@Test
public void allowanceTransferFromTest() throws Exception {
/*
* ChaincodeStub localStub = new ChaincodeStubNaiveImpl(); ((ChaincodeStubNaiveImpl)
* localStub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS); Context localCtx =
* mock(Context.class); ClientIdentity localidentity = new ClientIdentity(localStub);
* when(localCtx.getClientIdentity()).thenReturn(localidentity);
* when(localCtx.getStub()).thenReturn(localStub);
*/
String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
contract.approve(ctx, spender, "200");
String owner = ctx.getClientIdentity().getId();
((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
identity = new ClientIdentity(stub);
when(ctx.getClientIdentity()).thenReturn(identity);
when(ctx.getStub()).thenReturn(stub);
contract.transferFrom(ctx, owner, spender, "100");
long allowance = contract.allowance(ctx, owner, spender);
assertThat(allowance).isEqualTo(100);
}
}
}