diff --git a/asset-transfer-basic/chaincode-java/.gitignore b/asset-transfer-basic/chaincode-java/.gitignore new file mode 100644 index 00000000..bd6fd304 --- /dev/null +++ b/asset-transfer-basic/chaincode-java/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.gradle/ diff --git a/asset-transfer-basic/chaincode-java/README.md b/asset-transfer-basic/chaincode-java/README.md new file mode 100644 index 00000000..e069f568 --- /dev/null +++ b/asset-transfer-basic/chaincode-java/README.md @@ -0,0 +1,10 @@ + +## Basic Asset Transfer + +This sample implements the basic asset transfer scenario, illustrating the use of the Java Contract SDKs to provide a +smart contract as a service. + +To run this chaincode contract locally on a development network, see: + +- [Debugging chaincode as a service](../../test-network-k8s/docs/CHAINCODE_AS_A_SERVICE.md) (Kube test network) +- [End-to-end with the test-network](../../test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md#end-to-end-with-the-the-test-network) (Docker compose) diff --git a/asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh b/asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh index 7192c8da..1ff8e07e 100755 --- a/asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh +++ b/asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh @@ -7,10 +7,10 @@ set -euo pipefail : ${DEBUG:="false"} if [ "${DEBUG,,}" = "true" ]; then - java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar + exec java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then - java -jar /chaincode.jar # todo + exec java -jar /chaincode.jar # todo else - java -jar /chaincode.jar + exec java -jar /chaincode.jar fi diff --git a/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar b/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf0..7454180f 100644 Binary files a/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar and b/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties b/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties index bb8b2fc2..84d1f85f 100644 --- a/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties +++ b/asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/asset-transfer-basic/chaincode-java/gradlew b/asset-transfer-basic/chaincode-java/gradlew index 83f2acfd..1b6c7873 100755 --- a/asset-transfer-basic/chaincode-java/gradlew +++ b/asset-transfer-basic/chaincode-java/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,113 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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 @@ -105,84 +140,95 @@ 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=$((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" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# 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, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# 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" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/asset-transfer-basic/chaincode-java/gradlew.bat b/asset-transfer-basic/chaincode-java/gradlew.bat index 9618d8d9..107acd32 100644 --- a/asset-transfer-basic/chaincode-java/gradlew.bat +++ b/asset-transfer-basic/chaincode-java/gradlew.bat @@ -29,6 +29,9 @@ 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" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ 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% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index c1ace394..3399833c 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -130,13 +130,17 @@ jobs: displayName: Run Test Network Basic Chaincode - job: KubeTestNetworkBasic - displayName: Kube Test Network Basic + displayName: Kube Test Network pool: vmImage: ubuntu-20.04 strategy: matrix: - Docker-Typescript: + Typescript-Java: CLIENT_LANGUAGE: typescript + CHAINCODE_LANGUAGE: java + Typescript-Golang: + CLIENT_LANGUAGE: typescript + CHAINCODE_LANGUAGE: external steps: - template: templates/install-k8s-deps.yml - script: ../ci/scripts/run-k8s-test-network-basic.sh diff --git a/ci/scripts/run-k8s-test-network-basic.sh b/ci/scripts/run-k8s-test-network-basic.sh index 35e9e93d..86093238 100755 --- a/ci/scripts/run-k8s-test-network-basic.sh +++ b/ci/scripts/run-k8s-test-network-basic.sh @@ -9,6 +9,7 @@ set -euo pipefail # Test matrix parameters export CONTAINER_CLI=${CONTAINER_CLI:-docker} export CLIENT_LANGUAGE=${CLIENT_LANGUAGE:-typescript} +export CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-java} # Fabric version and Docker registry source: use the latest stable tag image from JFrog export FABRIC_VERSION=${FABRIC_VERSION:-2.4} @@ -19,9 +20,11 @@ export TEST_NETWORK_FABRIC_CA_VERSION=amd64-${FABRIC_VERSION}-stable # test-network-k8s parameters export TEST_TAG=$(git describe) export TEST_NETWORK_KIND_CLUSTER_NAME=${TEST_NETWORK_KIND_CLUSTER_NAME:-kind} + +# asset-transfer-basic chaincode target export TEST_NETWORK_CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic} -export TEST_NETWORK_CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_NAME}:${TEST_TAG} -export TEST_NETWORK_CHAINCODE_PATH=${TEST_NETWORK_CHAINCODE_PATH:-../asset-transfer-basic/chaincode-external} +export TEST_NETWORK_CHAINCODE_PATH=${TEST_NETWORK_CHAINCODE_PATH:-$PWD/../asset-transfer-basic/chaincode-${CHAINCODE_LANGUAGE}} +export TEST_NETWORK_CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_IMAGE:-fabric-samples/asset-transfer-basic/chaincode-${CHAINCODE_LANGUAGE}} # gateway client application parameters export GATEWAY_CLIENT_APPLICATION_PATH=${GATEWAY_CLIENT_APPLICATION_PATH:-../asset-transfer-basic/application-gateway-${CLIENT_LANGUAGE}} @@ -44,12 +47,10 @@ function print() { function touteSuite() { createCluster - buildChaincodeImage } function quitterLaScene() { destroyCluster - scrubCCImages } function createCluster() { @@ -62,26 +63,13 @@ function destroyCluster() { ./network unkind } -function buildChaincodeImage() { - print "Building chaincode image $TEST_NETWORK_CHAINCODE_IMAGE" - ${CONTAINER_CLI} build -t $TEST_NETWORK_CHAINCODE_IMAGE $TEST_NETWORK_CHAINCODE_PATH - - # todo: work with local reg, or k3s, or KIND, or ... - kind load docker-image $TEST_NETWORK_CHAINCODE_IMAGE -} - -function scrubCCImages() { - print "Scrubbing chaincode images" - ${CONTAINER_CLI} rmi $TEST_NETWORK_CHAINCODE_IMAGE -} - function createNetwork() { print "Launching network" ./network up ./network channel create print "Deploying chaincode" - ./network chaincode deploy + ./network chaincode deploy asset-transfer-basic basic_1.0 $TEST_NETWORK_CHAINCODE_PATH } function stopNetwork() { @@ -93,13 +81,13 @@ function stopNetwork() { touteSuite trap "quitterLaScene" EXIT -# invoke / query createNetwork print "Inserting and querying assets" -( ./network chaincode invoke '{"Args":["InitLedger"]}' \ +( ./network chaincode metadata $CHAINCODE_NAME \ + && ./network chaincode invoke $CHAINCODE_NAME '{"Args":["InitLedger"]}' \ && sleep 5 \ - && ./network chaincode query '{"Args":["ReadAsset","asset1"]}' ) + && ./network chaincode query $CHAINCODE_NAME '{"Args":["ReadAsset","asset1"]}' ) print "OK" print "Running rest-easy test" diff --git a/test-network-k8s/README.md b/test-network-k8s/README.md index 312b5d3c..ce3f9c43 100644 --- a/test-network-k8s/README.md +++ b/test-network-k8s/README.md @@ -22,6 +22,7 @@ _Fabric, Ahoy!_ - [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) - [jq](https://stedolan.github.io/jq/) + ## Quickstart Create a local Kubernetes cluster: @@ -33,17 +34,18 @@ Launch the network, create a channel, and deploy the [basic-asset-transfer](../a ```shell ./network up ./network channel create -./network chaincode deploy + +./network chaincode deploy asset-transfer-basic basic_1.0 $PWD/../asset-transfer-basic/chaincode-java ``` Invoke and query chaincode: ```shell -./network chaincode invoke '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' -./network chaincode query '{"Args":["ReadAsset","1"]}' +./network chaincode invoke asset-transfer-basic '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' +./network chaincode query asset-transfer-basic '{"Args":["ReadAsset","1"]}' ``` Access the blockchain with a [REST API](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/rest-api-typescript): -``` +```shell ./network rest-easy ``` diff --git a/test-network-k8s/chaincode/asset-transfer-basic-debug/connection.json b/test-network-k8s/chaincode/asset-transfer-basic-debug/connection.json deleted file mode 100644 index 030c8ee1..00000000 --- a/test-network-k8s/chaincode/asset-transfer-basic-debug/connection.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "address": "host.docker.internal:9999", - "dial_timeout": "10s", - "tls_required": false -} diff --git a/test-network-k8s/chaincode/asset-transfer-basic-debug/metadata.json b/test-network-k8s/chaincode/asset-transfer-basic-debug/metadata.json deleted file mode 100644 index ce13665f..00000000 --- a/test-network-k8s/chaincode/asset-transfer-basic-debug/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "ccaas", - "label": "basic_1.0" -} \ No newline at end of file diff --git a/test-network-k8s/chaincode/asset-transfer-basic/connection.json b/test-network-k8s/chaincode/asset-transfer-basic/connection.json deleted file mode 100644 index f3a8dd89..00000000 --- a/test-network-k8s/chaincode/asset-transfer-basic/connection.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "address": "{{.peername}}-cc-asset-transfer-basic:9999", - "dial_timeout": "10s", - "tls_required": false -} \ No newline at end of file diff --git a/test-network-k8s/chaincode/asset-transfer-basic/metadata.json b/test-network-k8s/chaincode/asset-transfer-basic/metadata.json deleted file mode 100644 index c52d2a5e..00000000 --- a/test-network-k8s/chaincode/asset-transfer-basic/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "ccaas", - "label": "basic_1.0" -} diff --git a/test-network-k8s/docs/CHAINCODE.md b/test-network-k8s/docs/CHAINCODE.md index 5047f3d6..8e09e83b 100644 --- a/test-network-k8s/docs/CHAINCODE.md +++ b/test-network-k8s/docs/CHAINCODE.md @@ -200,73 +200,11 @@ docker push $TEST_NETWORK_CHAINCODE_IMAGE One of the most compelling features of Fabric's _Chaincode-as-a-Service_ pattern is that when the peer connects to a chaincode URL, it can connect back to a port on the local host. Instead of connecting to a pod running in a -container within Kubernetes, we can simply connect to a native binary running in a debugger, an IDE, or docker image -running locally! +container within Kubernetes, the chaincode process can be launched locally as a native binary in a debugger, an IDE, +or a docker image bound to the host network. -Using a singular framework, we can employ this method to enable _rapid_ **edit/test/debug cycles** when authoring -code, **verify** docker images generated by a CI/CD pipeline, and run integration tests on a local Kubernetes. - -For example, we can deploy the basic asset transfer smart contract with a [connection.json](../chaincode/asset-transfer-basic-debug/connection.json) -referencing a service bound to the Docker network's IP address for the local host: -```json -{ - "address": "host.docker.internal:9999", -} -``` -When the test network opens a TCP socket to the chaincode process, the connection will be made from containers -running within Kubernetes to the port opened on the local system. Let's employ this to technique by running a -chaincode endpoint in a local Docker container, native binary, or IDE debugger: - - -0. Edit assetTransfer.go and [Build the Chaincode Image](#build-a-chaincode-docker-image) - - -1. Bring up the test network with: -```shell -$ ./network up -$ ./network channel create -``` - -2. Install the debug chaincode archive, using a connection to localhost:9999 : -```shell -$ export TEST_NETWORK_CHAINCODE_NAME=asset-transfer-basic-debug -$ export TEST_NETWORK_CHAINCODE_IMAGE=localhost:5000/asset-transfer-basic - -$ ./network chaincode install -Installing chaincode "asset-transfer-basic-debug": -✅ - Packaging chaincode folder chaincode/asset-transfer-basic-debug ... -✅ - Transferring chaincode archive to org1 ... -✅ - Installing chaincode for org org1 ... -🏁 - Chaincode is installed with CHAINCODE_ID=basic_1.0:159ed2f227586f40c5804e157919903fda2b861488f35eefb365eb9d85a73da3 -``` - -3. Set the `CHAINCODE_ID` and launch the chaincode binding to localhost:9999: -```shell -$ export CHAINCODE_ID=basic_1.0:159ed2f227586f40c5804e157919903fda2b861488f35eefb365eb9d85a73da3 - -$ docker run \ - --rm \ - --name asset-transfer-basic-debug \ - -e CHAINCODE_ID \ - -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 \ - -p 9999:9999 \ - localhost:5000/asset-transfer-basic -``` - -4. Activate the chaincode (commit and approve on the peer): -```shell -$ ./network chaincode activate -``` - -When the peer communicates with chaincode in this fashion, the network will reach out to the grpc server -bound to the localhost:9999, rather than connecting to services locked up behind the wall of Kubernetes -networking. - -As an exercise, try using this approach to: - -- introduce some `fmt.Printf` logging output to the chaincode, attaching to a process running locally in an IDE / debugger. -- build your local modifications into a docker container, publishing locally to localhost:5000/asset-transfer-basic -- test your local modifications by running a chaincode referencing the image hosted in the local container registry. +For additional details, see the [debugging chaincode](CHAINCODE_AS_A_SERVICE.md) guide for running the basic asset +transfer chaincode in an interactive development workflow. ## Next Steps: diff --git a/test-network-k8s/docs/CHAINCODE_AS_A_SERVICE.md b/test-network-k8s/docs/CHAINCODE_AS_A_SERVICE.md new file mode 100644 index 00000000..2afe0329 --- /dev/null +++ b/test-network-k8s/docs/CHAINCODE_AS_A_SERVICE.md @@ -0,0 +1,161 @@ +# Debugging Chaincode + +In this sample we will employ the [Kubernetes Test Network](../README.md) to illustrate a scenario of +building, running, and debugging chaincode on a development workstation. + +While this guide targets the Java [asset-transfer-basic](../../asset-transfer-basic/chaincode-java) sample, the approach +may be applied to any sample and chaincode implementation language. + +When debugging chaincode as a service, the chaincode process is launched on the local system, binding to a port +on the host's network interface. In this mode the developer has complete flexibility in determining how and where the +process runs - it can be launched as a native binary from a CLI, attached to an active debugging session from an IDE, +as a Docker container, or even behind a reverse network proxy for diagnosing issues in a remote / cloud-based Fabric +network. + + +## TL/DR + +``` +export PATH=${PWD}/test-network-k8s:$PATH + +cd asset-transfer-basic/chaincode-java + +network kind +``` +``` +network up +network channel create +``` +``` +network chaincode deploy asset-transfer-basic basic_1.0 ${PWD} +``` +``` +network chaincode metadata asset-transfer-basic +network chaincode invoke asset-transfer-basic '{"Args":["InitLedger"]}' +network chaincode query asset-transfer-basic '{"Args":["ReadAsset","asset1"]}' | jq +``` + +## Detailed Guide + +```shell +network down +network up +network channel create +``` + +```shell +# Build the chaincode docker image +docker build -t fabric-samples/asset-transfer-basic/chaincode-java . + +# Load the docker image directly to the KIND control plane. +# (Alternately, build/tag/push the image to a remote container registry, e.g. localhost:5000 or ghcr.io) +kind load docker-image fabric-samples/asset-transfer-basic/chaincode-java +``` + +```shell +# Assemble the chaincode package archive +network chaincode package basic_1.0 asset-transfer-basic $PWD/build/asset-transfer.tgz + +# Determine the ID for the chaincode package +CORE_CHAINCODE_ID_NAME=$(network chaincode id $PWD/build/asset-transfer.tgz) + +# Launch the chaincode in k8s as Deployment + Service +network chaincode launch asset-transfer-basic $CORE_CHAINCODE_ID_NAME fabric-samples/asset-transfer-basic/chaincode-java + +# Complete the chaincode lifecycle +network chaincode install $PWD/build/asset-transfer.tgz +network chaincode approve asset-transfer-basic $CORE_CHAINCODE_ID_NAME +network chaincode commit asset-transfer-basic +``` + +```shell +# execute the smart contract by name +network chaincode metadata asset-transfer-basic +network chaincode invoke asset-transfer-basic '{"Args":["InitLedger"]}' +network chaincode query asset-transfer-basic '{"Args":["ReadAsset","asset1"]}' +``` + +```shell +kubectl -n test-network logs -f deployment/org1peer1-ccaas-asset-transfer-basic +``` + +## Debugging + +### Build + +```shell +./gradlew shadowJar +``` +or +```shell +docker build -t fabric-samples/asset-transfer-basic/chaincode-java . +``` + + +### Package + +By instructing the peer to connect to chaincode at the Docker host alias `host.docker.internal`, pods running in +Kubernetes will access the local process via a special loopback interface established by KIND. + +Set the "address" attribute in the package connection.json descriptor and assemble the chaincode package: +```shell +export TEST_NETWORK_CHAINCODE_ADDRESS=host.docker.internal:9999 + +network cc package basic_1.0 asset-transfer-debug $PWD/build/asset-transfer-debug.tgz +``` + +### Launch + +When chaincode is launched locally, it must declare the package ID in the environment as if the process had been managed +by the peer's chaincode lifecycle manager. Calculate the package ID and start the chaincode, binding to port 9999 +on the local system: + +```shell +export CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 +export CORE_CHAINCODE_ID_NAME=$(network chaincode id $PWD/build/asset-transfer-debug.tgz) + +java -jar build/libs/chaincode.jar +``` + +Or using the editor/debugger/IDE of your choice, create a launch target for `ContractMain.main()`, specifying the +environment as above. + +Or launch the chaincode in a Docker container, binding to port 9999 on the host system: + +```shell +docker run \ + --rm \ + --name basic_1.0 \ + -p 9999:9999 \ + -e CHAINCODE_SERVER_ADDRESS \ + -e CORE_CHAINCODE_ID_NAME \ + fabric-samples/asset-transfer-basic/chaincode-java +``` + +### Approve, Invoke, and Query + +After the contract main has launched, install, approve, commit, and invoke the chaincode: + +```shell +# Complete the chaincode lifecycle +network cc activate asset-transfer-debug $PWD/build/asset-transfer-debug.tgz +``` + +```shell +# execute the smart contract by name +network cc metadata asset-transfer-debug +network cc invoke asset-transfer-debug '{"Args":["InitLedger"]}' +network cc query asset-transfer-debug '{"Args":["ReadAsset","asset1"]}' +``` + +## Tear Down + +```shell +network down +``` +or +```shell +network unkind +``` + + diff --git a/test-network-k8s/docs/README.md b/test-network-k8s/docs/README.md index 8c3abd17..62b673b6 100644 --- a/test-network-k8s/docs/README.md +++ b/test-network-k8s/docs/README.md @@ -41,5 +41,6 @@ _Chaincode-as-a-Service_ running in a shared Kubernetes namespace. - [Deploy Orderers and Peers](TEST_NETWORK.md#starting-peers-and-orderers) - [Working with Channels](CHANNELS.md) - [Working with Chaincode](CHAINCODE.md) +- [Debugging Chaincode](CHAINCODE_AS_A_SERVICE.md) - [Working with Applications](APPLICATIONS.md) - [High Availability](HIGH_AVAILABILITY.md) diff --git a/test-network-k8s/kube/org1/org1-cc-template.yaml b/test-network-k8s/kube/org1/org1-cc-template.yaml index c86c4208..05658f52 100644 --- a/test-network-k8s/kube/org1/org1-cc-template.yaml +++ b/test-network-k8s/kube/org1/org1-cc-template.yaml @@ -7,27 +7,28 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: org1{{PEER_NAME}}-cc-{{CHAINCODE_NAME}} + name: org1{{PEER_NAME}}-ccaas-{{CHAINCODE_NAME}} spec: replicas: 1 selector: matchLabels: - app: org1{{PEER_NAME}}-cc-{{CHAINCODE_NAME}} + app: org1{{PEER_NAME}}-ccaas-{{CHAINCODE_NAME}} template: metadata: labels: - app: org1{{PEER_NAME}}-cc-{{CHAINCODE_NAME}} + app: org1{{PEER_NAME}}-ccaas-{{CHAINCODE_NAME}} spec: containers: - name: main image: {{CHAINCODE_IMAGE}} + imagePullPolicy: IfNotPresent env: - name: CHAINCODE_SERVER_ADDRESS value: 0.0.0.0:9999 - - # todo: load with an envFrom and a dynamic config map with the ID. - name: CHAINCODE_ID value: {{CHAINCODE_ID}} + - name: CORE_CHAINCODE_ID_NAME + value: {{CHAINCODE_ID}} ports: - containerPort: 9999 @@ -35,11 +36,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: org1{{PEER_NAME}}-cc-{{CHAINCODE_NAME}} + name: org1{{PEER_NAME}}-ccaas-{{CHAINCODE_NAME}} spec: ports: - name: chaincode port: 9999 protocol: TCP selector: - app: org1{{PEER_NAME}}-cc-{{CHAINCODE_NAME}} \ No newline at end of file + app: org1{{PEER_NAME}}-ccaas-{{CHAINCODE_NAME}} \ No newline at end of file diff --git a/test-network-k8s/network b/test-network-k8s/network index b92f3c24..a607fd52 100755 --- a/test-network-k8s/network +++ b/test-network-k8s/network @@ -14,6 +14,9 @@ set -o errexit # todo: allow the user to specify the chaincode name (hardcoded as 'basic') both in install and invoke/query # todo: track down a nasty bug whereby the CA service endpoints (kube services) will occasionally reject TCP connections after network down/up. This is patched by introducing a 10s sleep after the deployments are up... +# todo: allow relative paths for input arguments. +cd "$(dirname "$0")" + export CONTAINER_CLI=${CONTAINER_CLI:-docker} export FABRIC_VERSION=${TEST_NETWORK_FABRIC_VERSION:-2.4.3} export FABRIC_CA_VERSION=${TEST_NETWORK_FABRIC_CA_VERSION:-1.5.2} @@ -33,15 +36,15 @@ LOCAL_REGISTRY_PORT=${TEST_NETWORK_LOCAL_REGISTRY_PORT:-5000} STAGE_DOCKER_IMAGES=${TEST_NETWORK_STAGE_DOCKER_IMAGES:-false} NGINX_HTTP_PORT=${TEST_NETWORK_INGRESS_HTTP_PORT:-80} NGINX_HTTPS_PORT=${TEST_NETWORK_INGRESS_HTTPS_PORT:-443} -CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic} -CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_IMAGE:-ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest} -CHAINCODE_LABEL=${TEST_NETWORK_CHAINCODE_LABEL:-basic_1.0} + # todo: more complicated config, as these bleed into the yaml descriptors (sed? kustomize? helm (no)? tkn? ansible?...) or other script locations TLSADMIN_AUTH=tlsadmin:tlsadminpw RCAADMIN_AUTH=rcaadmin:rcaadminpw function print_help() { + set +x + log log "--- Fabric Information" log "Fabric Version \t\t: ${FABRIC_VERSION}" @@ -51,11 +54,6 @@ function print_help() { log "Ingress domain \t\t: ${DOMAIN}" log "Channel name \t\t: ${CHANNEL_NAME}" log - log "--- Chaincode Information" - log "Chaincode name \t\t: ${CHAINCODE_NAME}" - log "Chaincode image \t: ${CHAINCODE_IMAGE}" - log "Chaincode label \t: ${CHAINCODE_LABEL}" - log log "--- Cluster Information" log "Cluster name \t\t: ${CLUSTER_NAME}" log "Cluster namespace \t: ${NS}" @@ -99,8 +97,6 @@ else shift fi - - if [ "${MODE}" == "kind" ]; then log "Initializing KIND cluster \"${CLUSTER_NAME}\":" kind_init @@ -127,10 +123,10 @@ elif [ "${MODE}" == "down" ]; then log "🏁 - Fabric network is down." elif [ "${MODE}" == "channel" ]; then - ACTION=$1 + COMMAND=$1 shift - if [ "${ACTION}" == "create" ]; then + if [ "${COMMAND}" == "create" ]; then log "Creating channel \"${CHANNEL_NAME}\":" channel_up log "🏁 - Channel is ready." @@ -140,37 +136,8 @@ elif [ "${MODE}" == "channel" ]; then exit 1 fi -elif [ "${MODE}" == "chaincode" ]; then - ACTION=$1 - shift - - if [ "${ACTION}" == "deploy" ]; then - log "Deploying chaincode \"${CHAINCODE_NAME}\":" - deploy_chaincode - log "🏁 - Chaincode is ready." - - elif [ "${ACTION}" == "install" ]; then - log "Installing chaincode \"${CHAINCODE_NAME}\":" - install_chaincode - log "🏁 - Chaincode is installed with CHAINCODE_ID=${CHAINCODE_ID}" - - elif [ "${ACTION}" == "activate" ]; then - log "Activating chaincode \"${CHAINCODE_NAME}\":" - activate_chaincode - log "🏁 - Chaincode is activated with CHAINCODE_ID=${CHAINCODE_ID}" - - elif [ "${ACTION}" == "invoke" ]; then - invoke_chaincode $@ 2>> ${LOG_FILE} - - elif [ "${ACTION}" == "query" ]; then - query_chaincode $@ >> ${LOG_FILE} - - elif [ "${ACTION}" == "metadata" ]; then - query_chaincode_metadata >> ${LOG_FILE} - else - print_help - exit 1 - fi +elif [[ "${MODE}" == "chaincode" || "${MODE}" == "cc" ]]; then + chaincode_command_group $@ elif [ "${MODE}" == "anchor" ]; then update_anchor_peers $@ diff --git a/test-network-k8s/scripts/chaincode.sh b/test-network-k8s/scripts/chaincode.sh index e1275609..7bcf04d2 100755 --- a/test-network-k8s/scripts/chaincode.sh +++ b/test-network-k8s/scripts/chaincode.sh @@ -5,14 +5,156 @@ # SPDX-License-Identifier: Apache-2.0 # -function package_chaincode_for() { - local org=$1 - local cc_folder="chaincode/${CHAINCODE_NAME}" - local build_folder="build/chaincode" - local cc_archive="${build_folder}/${CHAINCODE_NAME}.tgz" - push_fn "Packaging chaincode folder ${cc_folder}" +# Convenience routine to "do everything" required to bring up a sample CC. +function deploy_chaincode() { + local cc_name=$1 + local cc_label=$2 + local cc_folder=$(absolute_path $3) - mkdir -p ${build_folder} + local temp_folder=$(mktemp -d) + local cc_package=${temp_folder}/${cc_name}.tgz + + package_chaincode ${cc_label} ${cc_name} ${cc_package} + + set_chaincode_id ${cc_package} + set_chaincode_image ${cc_folder} + + build_chaincode_image ${cc_folder} ${CHAINCODE_IMAGE} + kind_load_image ${CHAINCODE_IMAGE} + launch_chaincode ${cc_name} ${CHAINCODE_ID} ${CHAINCODE_IMAGE} + + activate_chaincode ${cc_name} ${cc_package} +} + +# Infer a reasonable name for the chaincode image based on the folder path conventions, or +# allow the user to override with TEST_NETWORK_CHAINCODE_IMAGE. +function set_chaincode_image() { + local cc_folder=$1 + + if [ -z "$TEST_NETWORK_CHAINCODE_IMAGE" ]; then + # cc_folder path starting with first index of "fabric-samples" + CHAINCODE_IMAGE=${cc_folder/*fabric-samples/fabric-samples} + else + CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_IMAGE} + fi +} + +# Convenience routine to "do everything other than package and launch" a sample CC. +# When debugging a chaincode server, the process must be launched prior to completing +# the chaincode lifecycle at the peer. This routine provides a route for packaging +# and installing the chaincode out of band, and a single target to complete the peer +# chaincode lifecycle. +function activate_chaincode() { + local cc_name=$1 + local cc_package=$2 + + set_chaincode_id ${cc_package} + + install_chaincode ${cc_package} + approve_chaincode ${cc_name} ${CHAINCODE_ID} + commit_chaincode ${cc_name} +} + +function query_chaincode() { + local cc_name=$1 + shift + + set -x + + export_peer_context org1 peer1 + + peer chaincode query \ + -n $cc_name \ + -C $CHANNEL_NAME \ + -c $@ +} + +function query_chaincode_metadata() { + local cc_name=$1 + shift + + set -x + local args='{"Args":["org.hyperledger.fabric:GetMetadata"]}' + + log '' + log 'Org1-Peer1:' + export_peer_context org1 peer1 + peer chaincode query -n $cc_name -C $CHANNEL_NAME -c $args + + log '' + log 'Org1-Peer2:' + export_peer_context org1 peer2 + peer chaincode query -n $cc_name -C $CHANNEL_NAME -c $args +} + +function invoke_chaincode() { + local cc_name=$1 + shift + + export_peer_context org1 peer1 + + peer chaincode invoke \ + -n $cc_name \ + -C $CHANNEL_NAME \ + -c $@ \ + --orderer org0-orderer1.${DOMAIN}:443 \ + --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem + + sleep 2 +} + +function build_chaincode_image() { + local cc_folder=$1 + local cc_image=$2 + + push_fn "Building chaincode image ${cc_image}" + + docker build -t ${cc_image} ${cc_folder} + + pop_fn +} + +function kind_load_image() { + local cc_image=$1 + + push_fn "Loading chaincode to kind image plane" + + kind load docker-image ${cc_image} + + pop_fn +} + +function package_chaincode() { + local cc_label=$1 + local cc_name=$2 + local cc_archive=$3 + + local cc_folder=$(dirname $cc_archive) + local archive_name=$(basename $cc_archive) + + push_fn "Packaging chaincode ${cc_label}" + + mkdir -p ${cc_folder} + + # Allow the user to override the service URL for the endpoint. This allows, for instance, + # local debugging at the 'host.docker.internal' DNS alias. + local cc_default_address="{{.peername}}-ccaas-${cc_name}:9999" + local cc_address=${TEST_NETWORK_CHAINCODE_ADDRESS:-$cc_default_address} + + cat << EOF > ${cc_folder}/connection.json +{ + "address": "${cc_address}", + "dial_timeout": "10s", + "tls_required": false +} +EOF + + cat << EOF > ${cc_folder}/metadata.json +{ + "type": "ccaas", + "label": "${cc_label}" +} +EOF tar -C ${cc_folder} -zcf ${cc_folder}/code.tar.gz connection.json tar -C ${cc_folder} -zcf ${cc_archive} code.tar.gz metadata.json @@ -22,62 +164,97 @@ function package_chaincode_for() { pop_fn } -function install_chaincode_for() { - local org=$1 - local peer=$2 - push_fn "Installing chaincode for org ${org} peer ${peer}" - - export_peer_context $org $peer - - peer lifecycle chaincode install build/chaincode/${CHAINCODE_NAME}.tgz - - pop_fn -} - function launch_chaincode_service() { local org=$1 local peer=$2 - local cc_id=$3 - local cc_image=$4 + local cc_name=$3 + local cc_id=$4 + local cc_image=$5 push_fn "Launching chaincode container \"${cc_image}\"" # The chaincode endpoint needs to have the generated chaincode ID available in the environment. # This could be from a config map, a secret, or by directly editing the deployment spec. Here we'll keep # things simple by using sed to substitute script variables into a yaml template. cat kube/${org}/${org}-cc-template.yaml \ - | sed 's,{{CHAINCODE_NAME}},'${CHAINCODE_NAME}',g' \ + | sed 's,{{CHAINCODE_NAME}},'${cc_name}',g' \ | sed 's,{{CHAINCODE_ID}},'${cc_id}',g' \ | sed 's,{{CHAINCODE_IMAGE}},'${cc_image}',g' \ | sed 's,{{PEER_NAME}},'${peer}',g' \ | exec kubectl -n $NS apply -f - - kubectl -n $NS rollout status deploy/${org}${peer}-cc-${CHAINCODE_NAME} + kubectl -n $NS rollout status deploy/${org}${peer}-ccaas-${cc_name} pop_fn } -function activate_chaincode_for() { +function launch_chaincode() { + local org=org1 + local cc_name=$1 + local cc_id=$2 + local cc_image=$3 + + launch_chaincode_service ${org} peer1 ${cc_name} ${cc_id} ${cc_image} + launch_chaincode_service ${org} peer2 ${cc_name} ${cc_id} ${cc_image} +} + +function install_chaincode_for() { local org=$1 local peer=$2 - local cc_id=$3 - push_fn "Activating $org chaincode ${CHAINCODE_ID}" + local cc_package=$3 + push_fn "Installing chaincode for org ${org} peer ${peer}" + + export_peer_context $org $peer + + peer lifecycle chaincode install $cc_package + + pop_fn +} + +# Package and install the chaincode, but do not activate. +function install_chaincode() { + local org=org1 + local cc_package=$1 + + install_chaincode_for ${org} peer1 ${cc_package} + install_chaincode_for ${org} peer2 ${cc_package} +} + +# approve the chaincode package for an org and assign a name +function approve_chaincode() { + local org=org1 + local peer=peer1 + local cc_name=$1 + local cc_id=$2 + push_fn "Approving chaincode ${cc_name} with ID ${cc_id}" export_peer_context $org $peer peer lifecycle \ chaincode approveformyorg \ --channelID ${CHANNEL_NAME} \ - --name ${CHAINCODE_NAME} \ + --name ${cc_name} \ --version 1 \ --package-id ${cc_id} \ --sequence 1 \ --orderer org0-orderer1.${DOMAIN}:443 \ --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem + pop_fn +} + +# commit the named chaincode for an org +function commit_chaincode() { + local org=org1 + local peer=peer1 + local cc_name=$1 + push_fn "Committing chaincode ${cc_name}" + + export_peer_context $org $peer + peer lifecycle \ chaincode commit \ --channelID ${CHANNEL_NAME} \ - --name ${CHAINCODE_NAME} \ + --name ${cc_name} \ --version 1 \ --sequence 1 \ --orderer org0-orderer1.${DOMAIN}:443 \ @@ -86,90 +263,72 @@ function activate_chaincode_for() { pop_fn } -function query_chaincode() { - set -x - - export_peer_context org1 peer1 - - peer chaincode query \ - -n $CHAINCODE_NAME \ - -C $CHANNEL_NAME \ - -c $@ -} - -function query_chaincode_metadata() { - set -x - local args='{"Args":["org.hyperledger.fabric:GetMetadata"]}' - - log '' - log 'Org1-Peer1:' - export_peer_context org1 peer1 - peer chaincode query -n $CHAINCODE_NAME -C $CHANNEL_NAME -c $args - - log '' - log 'Org1-Peer2:' - export_peer_context org1 peer2 - peer chaincode query -n $CHAINCODE_NAME -C $CHANNEL_NAME -c $args -} - -function invoke_chaincode() { - # set -x - # todo: mangle additional $@ parameters with bash escape quotations - - export_peer_context org1 peer1 - - peer chaincode invoke \ - -n $CHAINCODE_NAME \ - -C $CHANNEL_NAME \ - -c $@ \ - --orderer org0-orderer1.${DOMAIN}:443 \ - --tls --cafile ${TEMP_DIR}/channel-msp/ordererOrganizations/org0/orderers/org0-orderer1/tls/signcerts/tls-cert.pem - - sleep 2 -} - -# Normally the chaincode ID is emitted by the peer install command. In this case, we'll generate the -# package ID as the sha-256 checksum of the chaincode archive. function set_chaincode_id() { - local cc_package=build/chaincode/${CHAINCODE_NAME}.tgz + local cc_package=$1 + cc_sha256=$(shasum -a 256 ${cc_package} | tr -s ' ' | cut -d ' ' -f 1) + cc_label=$(tar zxfO ${cc_package} metadata.json | jq -r '.label') - label=$( jq -r '.label' chaincode/${CHAINCODE_NAME}/metadata.json) - - CHAINCODE_ID=${label}:${cc_sha256} + CHAINCODE_ID=${cc_label}:${cc_sha256} } -# Package and install the chaincode, but do not activate. -function install_chaincode() { - local org=org1 +# chaincode "group" commands. Like "main" for chaincode sub-command group. +function chaincode_command_group() { + #set -x - package_chaincode_for ${org} + COMMAND=$1 + shift - install_chaincode_for ${org} peer1 - install_chaincode_for ${org} peer2 + if [ "${COMMAND}" == "deploy" ]; then + log "Deploying chaincode" + deploy_chaincode $@ + log "🏁 - Chaincode is ready." - set_chaincode_id + elif [ "${COMMAND}" == "activate" ]; then + log "Activating chaincode" + activate_chaincode $@ + log "🏁 - Chaincode is ready." + + elif [ "${COMMAND}" == "package" ]; then + log "Packaging chaincode" + package_chaincode $@ + log "🏁 - Chaincode package is ready." + + elif [ "${COMMAND}" == "id" ]; then + set_chaincode_id $@ + log $CHAINCODE_ID + + elif [ "${COMMAND}" == "launch" ]; then + log "Launching chaincode services" + launch_chaincode $@ + log "🏁 - Chaincode services are ready" + + elif [ "${COMMAND}" == "install" ]; then + log "Installing chaincode for org1" + install_chaincode $@ + log "🏁 - Chaincode is installed" + + elif [ "${COMMAND}" == "approve" ]; then + log "Approving chaincode for org1" + approve_chaincode $@ + log "🏁 - Chaincode is approved" + + elif [ "${COMMAND}" == "commit" ]; then + log "Committing chaincode for org1" + commit_chaincode $@ + log "🏁 - Chaincode is committed" + + elif [ "${COMMAND}" == "invoke" ]; then + invoke_chaincode $@ 2>> ${LOG_FILE} + + elif [ "${COMMAND}" == "query" ]; then + query_chaincode $@ >> ${LOG_FILE} + + elif [ "${COMMAND}" == "metadata" ]; then + query_chaincode_metadata $@ >> ${LOG_FILE} + + else + print_help + exit 1 + fi } - -# Activate the installed chaincode but do not package/install a new archive. -function activate_chaincode() { - set -x - - set_chaincode_id - activate_chaincode_for org1 peer1 $CHAINCODE_ID - - # jdk: does activation on a single peer apply to all peers in the org? This is an error: -# activate_chaincode_for org1 peer1 $CHAINCODE_ID - -} - -# Install, launch, and activate the chaincode -function deploy_chaincode() { - set -x - - install_chaincode - launch_chaincode_service org1 peer1 $CHAINCODE_ID $CHAINCODE_IMAGE - launch_chaincode_service org1 peer2 $CHAINCODE_ID $CHAINCODE_IMAGE - activate_chaincode -} - diff --git a/test-network-k8s/scripts/kind.sh b/test-network-k8s/scripts/kind.sh index f28d64d2..b1ae7878 100755 --- a/test-network-k8s/scripts/kind.sh +++ b/test-network-k8s/scripts/kind.sh @@ -213,4 +213,4 @@ function kind_init() { function kind_unkind() { kind_delete -} \ No newline at end of file +} diff --git a/test-network-k8s/scripts/utils.sh b/test-network-k8s/scripts/utils.sh index 0b97c07d..bc47209c 100644 --- a/test-network-k8s/scripts/utils.sh +++ b/test-network-k8s/scripts/utils.sh @@ -24,6 +24,7 @@ function logging_init() { function exit_fn() { rc=$? + set +x set +x @@ -101,3 +102,11 @@ function export_peer_context() { export CORE_PEER_MSPCONFIGPATH=${TEMP_DIR}/enrollments/${org}/users/${org}admin/msp export CORE_PEER_TLS_ROOTCERT_FILE=${TEMP_DIR}/channel-msp/peerOrganizations/${org}/msp/tlscacerts/tlsca-signcert.pem } + +function absolute_path() { + local relative_path=$1 + + local abspath="$( cd "${relative_path}" && pwd )" + + echo $abspath +}