se eliminarom mas ejemplos

This commit is contained in:
luc662 2025-05-17 21:36:46 +00:00
parent 2ed8904c0f
commit fd3acdb16e
129 changed files with 0 additions and 23841 deletions

View file

@ -1,3 +0,0 @@
.idea/
.gradle/
bin/

View file

@ -1,6 +0,0 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View file

@ -1,42 +0,0 @@
/*
* 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 {
// You can declare any Maven/Ivy/file repository here.
mavenCentral()
}
dependencies {
// This dependency is used by the application.
implementation 'com.google.guava:guava:29.0-jre'
implementation 'org.hyperledger.fabric:fabric-gateway-java:2.1.1'
}
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

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
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
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# 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"'
# 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 \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,92 +0,0 @@
@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=.
@rem This is normally unused
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% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 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!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,10 +0,0 @@
/*
* 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

@ -1,143 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// Running TestApp:
// gradle runApp
package application.java;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.hyperledger.fabric.gateway.Contract;
import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.Wallets;
public class App {
static {
System.setProperty("org.hyperledger.fabric.sdk.service_discovery.as_localhost", "true");
}
// helper function for getting connected to the gateway
public static Gateway connect() throws Exception{
// Load a file system based wallet for managing identities.
Path walletPath = Paths.get("wallet");
Wallet wallet = Wallets.newFileSystemWallet(walletPath);
// load a CCP
Path networkConfigPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com", "connection-org1.yaml");
Gateway.Builder builder = Gateway.createBuilder();
builder.identity(wallet, "appUser").networkConfig(networkConfigPath).discovery(true);
return builder.connect();
}
public static void main(String[] args) throws Exception {
// enrolls the admin and registers the user
try {
EnrollAdmin.main(null);
RegisterUser.main(null);
} catch (Exception e) {
System.err.println(e);
}
// connect to the network and invoke the smart contract
try (Gateway gateway = connect()) {
// get the network and contract
Network network = gateway.getNetwork("mychannel");
Contract contract = network.getContract("ledger");
byte[] result;
System.out.println("Submit Transaction: InitLedger creates the initial set of assets on the ledger.");
contract.submitTransaction("InitLedger");
System.out.println("\n");
// passing in 2 empty strings will query all the assets
result = contract.evaluateTransaction("GetAssetsByRange", "", "");
System.out.println("Evaluate Transaction: GetAssetsByRange, result: " + new String(result));
System.out.println("\n");
System.out.println("Submit Transaction: CreateAsset asset13");
// CreateAsset creates an asset with ID asset13, color yellow, owner Tom, size 5 and appraisedValue of 1300
contract.submitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300");
System.out.println("\n");
System.out.println("Evaluate Transaction: ReadAsset asset13");
// ReadAsset returns an asset with given assetID
result = contract.evaluateTransaction("ReadAsset", "asset13");
System.out.println("result: " + new String(result));
System.out.println("\n");
System.out.println("Evaluate Transaction: AssetExists asset1");
// AssetExists returns "true" if an asset with given assetID exist
result = contract.evaluateTransaction("AssetExists", "asset1");
System.out.println("result: " + new String(result));
System.out.println("\n");
System.out.println("Submit Transaction: DeleteAsset asset1");
contract.submitTransaction("DeleteAsset", "asset1");
System.out.println("\n");
System.out.println("Evaluate Transaction: AssetExists asset1");
// AssetExists returns "true" if an asset with given assetID exist
result = contract.evaluateTransaction("AssetExists", "asset1");
System.out.println("result: " + new String(result));
System.out.println("\n");
System.out.println("Submit Transaction: TransferAsset asset2 from owner Tomoko > owner Tom");
// TransferAsset transfers an asset with given ID to new owner Tom
contract.submitTransaction("TransferAsset", "asset2", "Tom");
// Rich Query with Pagination (Only supported if CouchDB is used as state database)
System.out.println("\n");
System.out.println("Evaluate Transaction:QueryAssetsWithPagination Tom's assets");
result = contract.evaluateTransaction("QueryAssetsWithPagination","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}","3","");
System.out.println("result: " + new String(result));
System.out.println("\n");
System.out.println("Submit Transaction: TransferAssetByColor yellow assets > newOwner Michel");
contract.submitTransaction("TransferAssetByColor", "yellow", "Michel");
// Rich Query (Only supported if CouchDB is used as state database):
System.out.println("\n");
System.out.println("Evaluate Transaction:QueryAssetsByOwner Michel");
result = contract.evaluateTransaction("QueryAssetsByOwner", "Michel");
System.out.println("result: " + new String(result));
System.out.println("\n");
System.out.println("Evaluate Transaction:GetAssetHistory asset13");
result = contract.evaluateTransaction("GetAssetHistory", "asset13");
System.out.println("result: " + new String(result));
// Rich Query (Only supported if CouchDB is used as state database):
System.out.println("\n");
System.out.println("Evaluate Transaction:QueryAssets assets of size 15");
result = contract.evaluateTransaction("QueryAssets", "{\"selector\":{\"size\":15}}");
System.out.println("result: " + new String(result));
// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
System.out.println("\n");
System.out.println("Evaluate Transaction:QueryAssets Jin Soo's assets");
result = contract.evaluateTransaction("QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Jin Soo\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}");
System.out.println("result: " + new String(result));
// Range Query with Pagination
System.out.println("\n");
System.out.println("Evaluate Transaction:GetAssetsByRangeWithPagination assets 3-5");
result = contract.evaluateTransaction("GetAssetsByRangeWithPagination", "asset3", "asset6", "3","");
System.out.println("result: " + new String(result));
}
catch(Exception e){
System.err.println(e);
System.exit(1);
}
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package application.java;
import java.nio.file.Paths;
import java.util.Properties;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.Wallets;
import org.hyperledger.fabric.gateway.Identities;
import org.hyperledger.fabric.gateway.Identity;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory;
import org.hyperledger.fabric_ca.sdk.EnrollmentRequest;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
public class EnrollAdmin {
public static void main(String[] args) throws Exception {
// Create a CA client for interacting with the CA.
Properties props = new Properties();
props.put("pemFile",
"../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem");
props.put("allowAllHostNames", "true");
HFCAClient caClient = HFCAClient.createNewInstance("https://localhost:7054", props);
CryptoSuite cryptoSuite = CryptoSuiteFactory.getDefault().getCryptoSuite();
caClient.setCryptoSuite(cryptoSuite);
// Create a wallet for managing identities
Wallet wallet = Wallets.newFileSystemWallet(Paths.get("wallet"));
// Check to see if we've already enrolled the admin user.
if (wallet.get("admin") != null) {
System.out.println("An identity for the admin user \"admin\" already exists in the wallet");
return;
}
// Enroll the admin user, and import the new identity into the wallet.
final EnrollmentRequest enrollmentRequestTLS = new EnrollmentRequest();
enrollmentRequestTLS.addHost("localhost");
enrollmentRequestTLS.setProfile("tls");
Enrollment enrollment = caClient.enroll("admin", "adminpw", enrollmentRequestTLS);
Identity user = Identities.newX509Identity("Org1MSP", enrollment);
wallet.put("admin", user);
System.out.println("Successfully enrolled user \"admin\" and imported it into the wallet");
}
}

View file

@ -1,107 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package application.java;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.util.Properties;
import java.util.Set;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.Wallets;
import org.hyperledger.fabric.gateway.Identities;
import org.hyperledger.fabric.gateway.Identity;
import org.hyperledger.fabric.gateway.X509Identity;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
public class RegisterUser {
public static void main(String[] args) throws Exception {
// Create a CA client for interacting with the CA.
Properties props = new Properties();
props.put("pemFile",
"../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem");
props.put("allowAllHostNames", "true");
HFCAClient caClient = HFCAClient.createNewInstance("https://localhost:7054", props);
CryptoSuite cryptoSuite = CryptoSuiteFactory.getDefault().getCryptoSuite();
caClient.setCryptoSuite(cryptoSuite);
// Create a wallet for managing identities
Wallet wallet = Wallets.newFileSystemWallet(Paths.get("wallet"));
// Check to see if we've already enrolled the user.
if (wallet.get("appUser") != null) {
System.out.println("An identity for the user \"appUser\" already exists in the wallet");
return;
}
X509Identity adminIdentity = (X509Identity)wallet.get("admin");
if (adminIdentity == null) {
System.out.println("\"admin\" needs to be enrolled and added to the wallet first");
return;
}
User admin = new User() {
@Override
public String getName() {
return "admin";
}
@Override
public Set<String> getRoles() {
return null;
}
@Override
public String getAccount() {
return null;
}
@Override
public String getAffiliation() {
return "org1.department1";
}
@Override
public Enrollment getEnrollment() {
return new Enrollment() {
@Override
public PrivateKey getKey() {
return adminIdentity.getPrivateKey();
}
@Override
public String getCert() {
return Identities.toPemString(adminIdentity.getCertificate());
}
};
}
@Override
public String getMspId() {
return "Org1MSP";
}
};
// Register the user, enroll the user, and import the new identity into the wallet.
RegistrationRequest registrationRequest = new RegistrationRequest("appUser");
registrationRequest.setAffiliation("org1.department1");
registrationRequest.setEnrollmentID("appUser");
String enrollmentSecret = caClient.register(registrationRequest, admin);
Enrollment enrollment = caClient.enroll("appUser", enrollmentSecret);
Identity user = Identities.newX509Identity("Org1MSP", enrollment);
wallet.put("appUser", user);
System.out.println("Successfully enrolled user \"appUser\" and imported it into the wallet");
}
}

View file

@ -1,19 +0,0 @@
# initialize root logger with level ERROR for stdout and fout
log4j.rootLogger=ERROR,stdout,fout
# set the log level for these components
log4j.logger.com.endeca=INFO
log4j.logger.com.endeca.itl.web.metrics=INFO
# add a ConsoleAppender to the logger stdout to write to the console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# use a simple message format
log4j.appender.stdout.layout.ConversionPattern=%m%n
# add a FileAppender to the logger fout
log4j.appender.fout=org.apache.log4j.FileAppender
# create a log file
log4j.appender.fout.File=crawl.log
log4j.appender.fout.layout=org.apache.log4j.PatternLayout
# use a more detailed message pattern
log4j.appender.fout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,37 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -1,14 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
package-lock.json
wallet
!wallet/.gitkeep

View file

@ -1,252 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const channelName = 'mychannel';
const chaincodeName = 'ledger';
const mspOrg1 = 'Org1MSP';
const walletPath = path.join(__dirname, 'wallet');
const userId = 'appUser';
function prettyJSONString(inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities, with the state database using couchdb
// ===> from directory /fabric-samples/test-network
// ./network.sh up createChannel -ca -s couchdb
// - Use any of the asset-transfer-ledger-queries chaincodes deployed on the channel "mychannel"
// with the chaincode name of "ledger". The following deploy command will package,
// install, approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory /fabric-samples/test-network
// ./network.sh deployCC -ccn ledger -ccp ../asset-transfer-ledger-queries/chaincode-javascript/ -ccl javascript
// - Be sure that node.js is installed
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
// npm install
// - to run this test application
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
// node app.js
// NOTE: If you see kind an error like these:
/*
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
OR
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
******** FAILED to run the application: Error: Identity not found in wallet: appUser
*/
// Delete the /fabric-samples/asset-transfer-ledger-queries/application-javascript/wallet directory
// and retry this application.
//
// The certificate authority must have been restarted and the saved certificates for the
// admin and application user are not valid. Deleting the wallet store will force these to be reset
// with the new certificate authority.
//
/**
* A test application to show ledger queries operations with any of the asset-transfer-ledger-queries chaincodes
* -- How to submit a transaction
* -- How to query and check the results
*
* To see the SDK workings, try setting the logging to show on the console before running
* export HFC_LOGGING='{"debug":"console"}'
*/
async function main() {
let skipInit = false;
if (process.argv.length > 2) {
if (process.argv[2] === 'skipInit') {
skipInit = true;
}
}
try {
// build an in memory object with the network configuration (also known as a connection profile)
const ccp = buildCCPOrg1();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com');
// setup the wallet to hold the credentials of the application user
const wallet = await buildWallet(Wallets, walletPath);
// in a real application this would be done on an administrative flow, and only once
await enrollAdmin(caClient, wallet, mspOrg1);
// in a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caClient, wallet, mspOrg1, userId, 'org1.department1');
// Create a new gateway instance for interacting with the fabric network.
// In a real application this would be done as the backend server session is setup for
// a user that has been verified.
const gateway = new Gateway();
try {
// setup the gateway instance
// The user will now be able to create connections to the fabric network and be able to
// submit transactions and query. All transactions submitted by this gateway will be
// signed by this user using the credentials stored in the wallet.
await gateway.connect(ccp, {
wallet,
identity: userId,
discovery: { enabled: true, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally
});
// Build a network instance based on the channel where the smart contract is deployed
const network = await gateway.getNetwork(channelName);
// Get the contract from the network.
const contract = network.getContract(chaincodeName);
// Initialize a set of asset data on the channel using the chaincode 'InitLedger' function.
// This type of transaction would only be run once by an application the first time it was started after it
// deployed the first time. Any updates to the chaincode deployed later would likely not need to run
// an "init" type function.
if (!skipInit) {
try {
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
await contract.submitTransaction('InitLedger');
console.log('*** Result: committed');
} catch (initError) {
// this is error is OK if we are rerunning this app without restarting
console.log(`******** initLedger failed :: ${initError}`);
}
} else {
console.log('*** not executing "InitLedger');
}
let result;
// Let's try a query operation (function).
// This will be sent to just one peer and the results will be shown.
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function returns assets in a specific range from asset1 to before asset6');
result = await contract.evaluateTransaction('GetAssetsByRange', 'asset1', 'asset6');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an open start and open end range to return assest1 to asset6');
result = await contract.evaluateTransaction('GetAssetsByRange', '', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an fixed start (asset3) and open end range to return assest3 to asset6');
result = await contract.evaluateTransaction('GetAssetsByRange', 'asset3', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an open start and fixed end (asset3) range to return assest1 to asset2');
result = await contract.evaluateTransaction('GetAssetsByRange', '', 'asset3');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Now let's try to submit a transaction.
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
// to the orderer to be committed by each of the peer's to the channel ledger.
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID(asset7), color(yellow), size(5), owner(Tom), and appraisedValue(1300) arguments');
await contract.submitTransaction('CreateAsset', 'asset7', 'yellow', '5', 'Tom', '1300');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset7)');
result = await contract.evaluateTransaction('ReadAsset', 'asset7');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with ID(asset7) exist');
result = await contract.evaluateTransaction('AssetExists', 'asset7');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Now let's try to submit a transaction that deletes an asset
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
// to the orderer to be committed by each of the peer's to the channel ledger.
console.log('\n--> Submit Transaction: DeleteAsset with ID(asset7)');
await contract.submitTransaction('DeleteAsset', 'asset7');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: AssetExists, function returns "false" if an asset with ID(asset7) does not exist');
result = await contract.evaluateTransaction('AssetExists', 'asset7');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: TransferAsset, transfer asset(asset2) to new owner(Max)');
await contract.submitTransaction('TransferAsset', 'asset2', 'Max');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset2)');
result = await contract.evaluateTransaction('ReadAsset', 'asset2');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query with Pagination (Only supported if CouchDB is used as state database)
console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Max" assets');
result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Max"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Recover the bookmark from previous query. Normally it will be inside a variable.
const resultJson = JSON.parse(result.toString());
console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Max" assets next page');
result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Max"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}', '1', resultJson.bookmark);
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: TransferAssetByColor, transfer all yellow assets to new owner(Michel)');
await contract.submitTransaction('TransferAssetByColor', 'yellow', 'Michel');
console.log('*** Result: committed');
// Rich Query (Only supported if CouchDB is used as state database):
console.log('\n--> Evaluate Transaction: QueryAssetsByOwner, find all assets with owner(Michel)');
result = await contract.evaluateTransaction('QueryAssetsByOwner', 'Michel');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: GetAssetHistory, get the history of an asset(asset7)');
result = await contract.evaluateTransaction('GetAssetHistory', 'asset7');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query (Only supported if CouchDB is used as state database):
console.log('\n--> Evaluate Transaction: QueryAssets, assets of size 15');
result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"size":15}}');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
console.log('\n--> Evaluate Transaction: QueryAssets, Jin Soo\'s assets');
result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"docType":"asset","owner":"Jin Soo"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Range Query with Pagination
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 1 of assets from asset2 to asset6 (asset2, asset3)');
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset2', 'asset6', '2', '');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Range Query with Pagination
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 2 of assets from asset2 to asset6 (asset4, asset5)');
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset2', 'asset6', '2', 'asset4');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('*** all tests completed');
} finally {
// Disconnect from the gateway when the application is closing
// This will close all connections to the network
gateway.disconnect();
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
process.exit(1);
}
console.log('*** application ending');
}
main();

View file

@ -1,23 +0,0 @@
{
"name": "asset-transfer-ledger-queries",
"version": "1.0.0",
"description": "Asset transfer ledger queries application implemented in JavaScript",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"scripts": {
"run": "node app.js",
"lint": "eslint *.js"
},
"dependencies": {
"fabric-ca-client": "^2.2.19",
"fabric-network": "^2.2.19"
},
"devDependencies": {
"eslint": "^7.32.0"
}
}

View file

@ -1 +0,0 @@
{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}

View file

@ -1,469 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
/*
====CHAINCODE EXECUTION SAMPLES (CLI) ==================
==== Invoke assets ====
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset1","blue","5","tom","35"]}'
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset2","red","4","tom","50"]}'
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset3","blue","6","tom","70"]}'
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAsset","asset2","jerry"]}'
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAssetByColor","blue","jerry"]}'
peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["DeleteAsset","asset1"]}'
==== Query assets ====
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["ReadAsset","asset1"]}'
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetsByRange","asset1","asset3"]}'
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetHistory","asset1"]}'
Rich Query (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsByOwner","tom"]}'
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"owner\":\"tom\"}}"]}'
Rich Query with Pagination (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}'
INDEXES TO SUPPORT COUCHDB RICH QUERIES
Indexes in CouchDB are required in order to make JSON queries efficient and are required for
any JSON query with a sort. Indexes may be packaged alongside
chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own
text file with extension *.json with the index definition formatted in JSON following the
CouchDB index JSON syntax as documented at:
http://docs.couchdb.org/en/2.3.1/api/database/find.html#db-index
This asset transfer ledger example chaincode demonstrates a packaged
index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json.
If you have access to the your peer's CouchDB state database in a development environment,
you may want to iteratively test various indexes in support of your chaincode queries. You
can use the CouchDB Fauxton interface or a command line curl utility to create and update
indexes. Then once you finalize an index, include the index definition alongside your
chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment
to managed environments.
In the examples below you can find index definitions that support asset transfer ledger
chaincode queries, along with the syntax that you can use in development environments
to create the indexes in the CouchDB Fauxton interface or a curl command line utility.
Index for docType, owner.
Example curl command line to define index in the CouchDB channel_chaincode database
curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
Index for docType, owner, size (descending order).
Example curl command line to define index in the CouchDB channel_chaincode database:
curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}'
Rich Query with index design doc specified only (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":{\"$eq\":\"asset\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}'
*/
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
const index = "color~name"
// SimpleChaincode implements the fabric-contract-api-go programming model
type SimpleChaincode struct {
contractapi.Contract
}
type Asset struct {
DocType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
ID string `json:"ID"` //the field tags are needed to keep case from bouncing around
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
AppraisedValue int `json:"appraisedValue"`
}
// HistoryQueryResult structure used for returning result of history query
type HistoryQueryResult struct {
Record *Asset `json:"record"`
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
IsDelete bool `json:"isDelete"`
}
// PaginatedQueryResult structure used for returning paginated query results and metadata
type PaginatedQueryResult struct {
Records []*Asset `json:"records"`
FetchedRecordsCount int32 `json:"fetchedRecordsCount"`
Bookmark string `json:"bookmark"`
}
// CreateAsset initializes a new asset in the ledger
func (t *SimpleChaincode) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, color string, size int, owner string, appraisedValue int) error {
exists, err := t.AssetExists(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
if exists {
return fmt.Errorf("asset already exists: %s", assetID)
}
asset := &Asset{
DocType: "asset",
ID: assetID,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetBytes, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(assetID, assetBytes)
if err != nil {
return err
}
// Create an index to enable color-based range queries, e.g. return all blue assets.
// An 'index' is a normal key-value entry in the ledger.
// The key is a composite key, with the elements that you want to range query on listed first.
// In our case, the composite key is based on indexName~color~name.
// This will enable very efficient state range queries based on composite keys matching indexName~color~*
colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(index, []string{asset.Color, asset.ID})
if err != nil {
return err
}
// Save index entry to world state. Only the key name is needed, no need to store a duplicate copy of the asset.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
value := []byte{0x00}
return ctx.GetStub().PutState(colorNameIndexKey, value)
}
// ReadAsset retrieves an asset from the ledger
func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
assetBytes, err := ctx.GetStub().GetState(assetID)
if err != nil {
return nil, fmt.Errorf("failed to get asset %s: %v", assetID, err)
}
if assetBytes == nil {
return nil, fmt.Errorf("asset %s does not exist", assetID)
}
var asset Asset
err = json.Unmarshal(assetBytes, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
// DeleteAsset removes an asset key-value pair from the ledger
func (t *SimpleChaincode) DeleteAsset(ctx contractapi.TransactionContextInterface, assetID string) error {
asset, err := t.ReadAsset(ctx, assetID)
if err != nil {
return err
}
err = ctx.GetStub().DelState(assetID)
if err != nil {
return fmt.Errorf("failed to delete asset %s: %v", assetID, err)
}
colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(index, []string{asset.Color, asset.ID})
if err != nil {
return err
}
// Delete index entry
return ctx.GetStub().DelState(colorNameIndexKey)
}
// TransferAsset transfers an asset by setting a new owner name on the asset
func (t *SimpleChaincode) TransferAsset(ctx contractapi.TransactionContextInterface, assetID, newOwner string) error {
asset, err := t.ReadAsset(ctx, assetID)
if err != nil {
return err
}
asset.Owner = newOwner
assetBytes, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(assetID, assetBytes)
}
// constructQueryResponseFromIterator constructs a slice of assets from the resultsIterator
func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) ([]*Asset, error) {
var assets []*Asset
for resultsIterator.HasNext() {
queryResult, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(queryResult.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
// GetAssetsByRange performs a range query based on the start and end keys provided.
// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
func (t *SimpleChaincode) GetAssetsByRange(ctx contractapi.TransactionContextInterface, startKey, endKey string) ([]*Asset, error) {
resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
return constructQueryResponseFromIterator(resultsIterator)
}
// TransferAssetByColor will transfer assets of a given color to a certain new owner.
// Uses GetStateByPartialCompositeKey (range query) against color~name 'index'.
// Committing peers will re-execute range queries to guarantee that result sets are stable
// between endorsement time and commit time. The transaction is invalidated by the
// committing peers if the result set has changed between endorsement time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// Example: GetStateByPartialCompositeKey/RangeQuery
func (t *SimpleChaincode) TransferAssetByColor(ctx contractapi.TransactionContextInterface, color, newOwner string) error {
// Execute a key range query on all keys starting with 'color'
coloredAssetResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(index, []string{color})
if err != nil {
return err
}
defer coloredAssetResultsIterator.Close()
for coloredAssetResultsIterator.HasNext() {
responseRange, err := coloredAssetResultsIterator.Next()
if err != nil {
return err
}
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(responseRange.Key)
if err != nil {
return err
}
if len(compositeKeyParts) > 1 {
returnedAssetID := compositeKeyParts[1]
asset, err := t.ReadAsset(ctx, returnedAssetID)
if err != nil {
return err
}
asset.Owner = newOwner
assetBytes, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(returnedAssetID, assetBytes)
if err != nil {
return fmt.Errorf("transfer failed for asset %s: %v", returnedAssetID, err)
}
}
}
return nil
}
// QueryAssetsByOwner queries for assets based on the owners name.
// This is an example of a parameterized query where the query logic is baked into the chaincode,
// and accepting a single query parameter (owner).
// Only available on state databases that support rich query (e.g. CouchDB)
// Example: Parameterized rich query
func (t *SimpleChaincode) QueryAssetsByOwner(ctx contractapi.TransactionContextInterface, owner string) ([]*Asset, error) {
queryString := fmt.Sprintf(`{"selector":{"docType":"asset","owner":"%s"}}`, owner)
return getQueryResultForQueryString(ctx, queryString)
}
// QueryAssets uses a query string to perform a query for assets.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// Example: Ad hoc rich query
func (t *SimpleChaincode) QueryAssets(ctx contractapi.TransactionContextInterface, queryString string) ([]*Asset, error) {
return getQueryResultForQueryString(ctx, queryString)
}
// getQueryResultForQueryString executes the passed in query string.
// The result set is built and returned as a byte array containing the JSON results.
func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, queryString string) ([]*Asset, error) {
resultsIterator, err := ctx.GetStub().GetQueryResult(queryString)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
return constructQueryResponseFromIterator(resultsIterator)
}
// GetAssetsByRangeWithPagination performs a range query based on the start and end key,
// page size and a bookmark.
// The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions.
// Example: Pagination with Range Query
func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.TransactionContextInterface, startKey string, endKey string, pageSize int, bookmark string) (*PaginatedQueryResult, error) {
resultsIterator, responseMetadata, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
assets, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}
return &PaginatedQueryResult{
Records: assets,
FetchedRecordsCount: responseMetadata.FetchedRecordsCount,
Bookmark: responseMetadata.Bookmark,
}, nil
}
// QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query
// for assets. Query string matching state database syntax is passed in and executed as is.
// The number of fetched records would be equal to or lesser than the specified page size.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// Paginated queries are only valid for read only transactions.
// Example: Pagination with Ad hoc Rich Query
func (t *SimpleChaincode) QueryAssetsWithPagination(ctx contractapi.TransactionContextInterface, queryString string, pageSize int, bookmark string) (*PaginatedQueryResult, error) {
return getQueryResultForQueryStringWithPagination(ctx, queryString, int32(pageSize), bookmark)
}
// getQueryResultForQueryStringWithPagination executes the passed in query string with
// pagination info. The result set is built and returned as a byte array containing the JSON results.
func getQueryResultForQueryStringWithPagination(ctx contractapi.TransactionContextInterface, queryString string, pageSize int32, bookmark string) (*PaginatedQueryResult, error) {
resultsIterator, responseMetadata, err := ctx.GetStub().GetQueryResultWithPagination(queryString, pageSize, bookmark)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
assets, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}
return &PaginatedQueryResult{
Records: assets,
FetchedRecordsCount: responseMetadata.FetchedRecordsCount,
Bookmark: responseMetadata.Bookmark,
}, nil
}
// GetAssetHistory returns the chain of custody for an asset since issuance.
func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]HistoryQueryResult, error) {
log.Printf("GetAssetHistory: ID %v", assetID)
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var records []HistoryQueryResult
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
if len(response.Value) > 0 {
err = json.Unmarshal(response.Value, &asset)
if err != nil {
return nil, err
}
} else {
asset = Asset{
ID: assetID,
}
}
record := HistoryQueryResult{
TxId: response.TxId,
Timestamp: response.Timestamp.AsTime(),
Record: &asset,
IsDelete: response.IsDelete,
}
records = append(records, record)
}
return records, nil
}
// AssetExists returns true when asset with given ID exists in the ledger.
func (t *SimpleChaincode) AssetExists(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
assetBytes, err := ctx.GetStub().GetState(assetID)
if err != nil {
return false, fmt.Errorf("failed to read asset %s from world state. %v", assetID, err)
}
return assetBytes != nil, nil
}
// InitLedger creates the initial set of assets in the ledger.
func (t *SimpleChaincode) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{DocType: "asset", ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{DocType: "asset", ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{DocType: "asset", ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{DocType: "asset", ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{DocType: "asset", ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{DocType: "asset", ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
for _, asset := range assets {
err := t.CreateAsset(ctx, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue)
if err != nil {
return err
}
}
return nil
}
func main() {
chaincode, err := contractapi.NewChaincode(&SimpleChaincode{})
if err != nil {
log.Panicf("Error creating asset chaincode: %v", err)
}
if err := chaincode.Start(); err != nil {
log.Panicf("Error starting asset chaincode: %v", err)
}
}

View file

@ -1,34 +0,0 @@
module github.com/hyperledger/fabric-samples/asset-transfer-ledger-queries/chaincode-go
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0
)
require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,37 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -1 +0,0 @@
{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}

View file

@ -1,13 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const CC = require('./lib/asset_transfer_ledger_chaincode.js');
module.exports.CC = CC;
module.exports.contracts = [ CC ];

View file

@ -1,411 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// ====CHAINCODE EXECUTION SAMPLES (CLI) ==================
// ==== Invoke assets ====
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["CreateAsset","asset1","blue","35","Tom","100"]}'
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["CreateAsset","asset2","red","50","Tom","150"]}'
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["CreateAsset","asset3","blue","70","Tom","200"]}'
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["TransferAsset","asset2","jerry"]}'
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["TransferAssetByColor","blue","jerry"]}'
// peer chaincode invoke -C CHANNEL_NAME -n asset_transfer -c '{"Args":["DeleteAsset","asset1"]}'
// ==== Query assets ====
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["ReadAsset","asset1"]}'
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["GetAssetsByRange","asset1","asset3"]}'
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["GetAssetHistory","asset1"]}'
// Rich Query (Only supported if CouchDB is used as state database):
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["QueryAssetsByOwner","Tom"]}' output issue
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"owner\":\"Tom\"}}"]}'
// Rich Query with Pagination (Only supported if CouchDB is used as state database):
// peer chaincode query -C CHANNEL_NAME -n asset_transfer -c '{"Args":["QueryAssetsWithPagination","{\"selector\":{\"owner\":\"Tom\"}}","3",""]}'
// INDEXES TO SUPPORT COUCHDB RICH QUERIES
//
// Indexes in CouchDB are required in order to make JSON queries efficient and are required for
// any JSON query with a sort. Indexes may be packaged alongside
// chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own
// text file with extension *.json with the index definition formatted in JSON following the
// CouchDB index JSON syntax as documented at:
// http://docs.couchdb.org/en/2.3.1/api/database/find.html#db-index
//
// This asset transfer ledger example chaincode demonstrates a packaged
// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json.
//
// If you have access to the your peer's CouchDB state database in a development environment,
// you may want to iteratively test various indexes in support of your chaincode queries. You
// can use the CouchDB Fauxton interface or a command line curl utility to create and update
// indexes. Then once you finalize an index, include the index definition alongside your
// chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment
// to managed environments.
//
// In the examples below you can find index definitions that support asset transfer ledger
// chaincode queries, along with the syntax that you can use in development environments
// to create the indexes in the CouchDB Fauxton interface or a curl command line utility.
//
// Index for docType, owner.
//
// Example curl command line to define index in the CouchDB channel_chaincode database
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
//
// Index for docType, owner, size (descending order).
//
// Example curl command line to define index in the CouchDB channel_chaincode database
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
// peer chaincode query -C CHANNEL_NAME -n ledger -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"Tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}'
// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database):
// peer chaincode query -C CHANNEL_NAME -n ledger -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":{\"$eq\":\"asset\"},\"owner\":{\"$eq\":\"Tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}'
'use strict';
const {Contract} = require('fabric-contract-api');
class Chaincode extends Contract {
// CreateAsset - create a new asset, store into chaincode state
async CreateAsset(ctx, assetID, color, size, owner, appraisedValue) {
const exists = await this.AssetExists(ctx, assetID);
if (exists) {
throw new Error(`The asset ${assetID} already exists`);
}
// ==== Create asset object and marshal to JSON ====
let asset = {
docType: 'asset',
assetID: assetID,
color: color,
size: size,
owner: owner,
appraisedValue: appraisedValue
};
// === Save asset to state ===
await ctx.stub.putState(assetID, Buffer.from(JSON.stringify(asset)));
let indexName = 'color~name';
let colorNameIndexKey = await ctx.stub.createCompositeKey(indexName, [asset.color, asset.assetID]);
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
await ctx.stub.putState(colorNameIndexKey, Buffer.from('\u0000'));
}
// ReadAsset returns the asset stored in the world state with given id.
async ReadAsset(ctx, id) {
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
if (!assetJSON || assetJSON.length === 0) {
throw new Error(`Asset ${id} does not exist`);
}
return assetJSON.toString();
}
// delete - remove a asset key/value pair from state
async DeleteAsset(ctx, id) {
if (!id) {
throw new Error('Asset name must not be empty');
}
let exists = await this.AssetExists(ctx, id);
if (!exists) {
throw new Error(`Asset ${id} does not exist`);
}
// to maintain the color~name index, we need to read the asset first and get its color
let valAsbytes = await ctx.stub.getState(id); // get the asset from chaincode state
let jsonResp = {};
if (!valAsbytes) {
jsonResp.error = `Asset does not exist: ${id}`;
throw new Error(jsonResp);
}
let assetJSON;
try {
assetJSON = JSON.parse(valAsbytes.toString());
} catch (err) {
jsonResp = {};
jsonResp.error = `Failed to decode JSON of: ${id}`;
throw new Error(jsonResp);
}
await ctx.stub.deleteState(id); //remove the asset from chaincode state
// delete the index
let indexName = 'color~name';
let colorNameIndexKey = ctx.stub.createCompositeKey(indexName, [assetJSON.color, assetJSON.assetID]);
if (!colorNameIndexKey) {
throw new Error(' Failed to create the createCompositeKey');
}
// Delete index entry to state.
await ctx.stub.deleteState(colorNameIndexKey);
}
// TransferAsset transfers a asset by setting a new owner name on the asset
async TransferAsset(ctx, assetName, newOwner) {
let assetAsBytes = await ctx.stub.getState(assetName);
if (!assetAsBytes || !assetAsBytes.toString()) {
throw new Error(`Asset ${assetName} does not exist`);
}
let assetToTransfer = {};
try {
assetToTransfer = JSON.parse(assetAsBytes.toString()); //unmarshal
} catch (err) {
let jsonResp = {};
jsonResp.error = 'Failed to decode JSON of: ' + assetName;
throw new Error(jsonResp);
}
assetToTransfer.owner = newOwner; //change the owner
let assetJSONasBytes = Buffer.from(JSON.stringify(assetToTransfer));
await ctx.stub.putState(assetName, assetJSONasBytes); //rewrite the asset
}
// GetAssetsByRange performs a range query based on the start and end keys provided.
// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
async GetAssetsByRange(ctx, startKey, endKey) {
let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
let results = await this._GetAllResults(resultsIterator, false);
return JSON.stringify(results);
}
// TransferAssetByColor will transfer assets of a given color to a certain new owner.
// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'.
// Committing peers will re-execute range queries to guarantee that result sets are stable
// between endorsement time and commit time. The transaction is invalidated by the
// committing peers if the result set has changed between endorsement time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// Example: GetStateByPartialCompositeKey/RangeQuery
async TransferAssetByColor(ctx, color, newOwner) {
// Query the color~name index by color
// This will execute a key range query on all keys starting with 'color'
let coloredAssetResultsIterator = await ctx.stub.getStateByPartialCompositeKey('color~name', [color]);
// Iterate through result set and for each asset found, transfer to newOwner
let responseRange = await coloredAssetResultsIterator.next();
while (!responseRange.done) {
if (!responseRange || !responseRange.value || !responseRange.value.key) {
return;
}
let objectType;
let attributes;
(
{objectType, attributes} = await ctx.stub.splitCompositeKey(responseRange.value.key)
);
console.log(objectType);
let returnedAssetName = attributes[1];
// Now call the transfer function for the found asset.
// Re-use the same function that is used to transfer individual assets
await this.TransferAsset(ctx, returnedAssetName, newOwner);
responseRange = await coloredAssetResultsIterator.next();
}
}
// QueryAssetsByOwner queries for assets based on a passed in owner.
// This is an example of a parameterized query where the query logic is baked into the chaincode,
// and accepting a single query parameter (owner).
// Only available on state databases that support rich query (e.g. CouchDB)
// Example: Parameterized rich query
async QueryAssetsByOwner(ctx, owner) {
let queryString = {};
queryString.selector = {};
queryString.selector.docType = 'asset';
queryString.selector.owner = owner;
return await this.GetQueryResultForQueryString(ctx, JSON.stringify(queryString)); //shim.success(queryResults);
}
// Example: Ad hoc rich query
// QueryAssets uses a query string to perform a query for assets.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
async QueryAssets(ctx, queryString) {
return await this.GetQueryResultForQueryString(ctx, queryString);
}
// GetQueryResultForQueryString executes the passed in query string.
// Result set is built and returned as a byte array containing the JSON results.
async GetQueryResultForQueryString(ctx, queryString) {
let resultsIterator = await ctx.stub.getQueryResult(queryString);
let results = await this._GetAllResults(resultsIterator, false);
return JSON.stringify(results);
}
// Example: Pagination with Range Query
// GetAssetsByRangeWithPagination performs a range query based on the start & end key,
// page size and a bookmark.
// The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions.
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
const {iterator, metadata} = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
let results = {};
results.results = await this._GetAllResults(iterator, false);
results.fetchedRecordsCount = metadata.fetchedRecordsCount;
results.bookmark = metadata.bookmark;
return JSON.stringify(results);
}
// Example: Pagination with Ad hoc Rich Query
// QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query
// for assets. Query string matching state database syntax is passed in and executed as is.
// The number of fetched records would be equal to or lesser than the specified page size.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// Paginated queries are only valid for read only transactions.
async QueryAssetsWithPagination(ctx, queryString, pageSize, bookmark) {
const {iterator, metadata} = await ctx.stub.getQueryResultWithPagination(queryString, pageSize, bookmark);
let results = {};
results.results = await this._GetAllResults(iterator, false);
results.fetchedRecordsCount = metadata.fetchedRecordsCount;
results.bookmark = metadata.bookmark;
return JSON.stringify(results);
}
// GetAssetHistory returns the chain of custody for an asset since issuance.
async GetAssetHistory(ctx, assetName) {
let resultsIterator = await ctx.stub.getHistoryForKey(assetName);
let results = await this._GetAllResults(resultsIterator, true);
return JSON.stringify(results);
}
// AssetExists returns true when asset with given ID exists in world state
async AssetExists(ctx, assetName) {
// ==== Check if asset already exists ====
let assetState = await ctx.stub.getState(assetName);
return assetState && assetState.length > 0;
}
// This is JavaScript so without Funcation Decorators, all functions are assumed
// to be transaction functions
//
// For internal functions... prefix them with _
async _GetAllResults(iterator, isHistory) {
let allResults = [];
let res = await iterator.next();
while (!res.done) {
if (res.value && res.value.value.toString()) {
let jsonRes = {};
console.log(res.value.value.toString('utf8'));
if (isHistory && isHistory === true) {
jsonRes.TxId = res.value.txId;
jsonRes.Timestamp = res.value.timestamp;
try {
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
console.log(err);
jsonRes.Value = res.value.value.toString('utf8');
}
} else {
jsonRes.Key = res.value.key;
try {
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
console.log(err);
jsonRes.Record = res.value.value.toString('utf8');
}
}
allResults.push(jsonRes);
}
res = await iterator.next();
}
iterator.close();
return allResults;
}
// InitLedger creates sample assets in the ledger
async InitLedger(ctx) {
const assets = [
{
assetID: 'asset1',
color: 'blue',
size: 5,
owner: 'Tom',
appraisedValue: 100
},
{
assetID: 'asset2',
color: 'red',
size: 5,
owner: 'Brad',
appraisedValue: 100
},
{
assetID: 'asset3',
color: 'green',
size: 10,
owner: 'Jin Soo',
appraisedValue: 200
},
{
assetID: 'asset4',
color: 'yellow',
size: 10,
owner: 'Max',
appraisedValue: 200
},
{
assetID: 'asset5',
color: 'black',
size: 15,
owner: 'Adriana',
appraisedValue: 250
},
{
assetID: 'asset6',
color: 'white',
size: 15,
owner: 'Michel',
appraisedValue: 250
},
];
for (const asset of assets) {
await this.CreateAsset(
ctx,
asset.assetID,
asset.color,
asset.size,
asset.owner,
asset.appraisedValue
);
}
}
}
module.exports = Chaincode;

File diff suppressed because it is too large Load diff

View file

@ -1,23 +0,0 @@
{
"name": "asset-transfer-ledger-queries",
"version": "1.0.0",
"description": "asset chaincode implemented in node.js",
"main": "index.js",
"engines": {
"node": ">=18"
},
"scripts": {
"start": "fabric-chaincode-node start",
"lint": "eslint *.js */**.js",
"test": ""
},
"engine-strict": true,
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "~2.5",
"fabric-shim": "~2.5"
},
"devDependencies": {
"eslint": "^8.57.0"
}
}

View file

@ -1,169 +0,0 @@
# State-based endorsement asset transfer sample
Transactions that are submitted to Hyperledger Fabric networks need to be endorsed
by peers that are joined to a channel before the transaction can be added to the
ledger. Fabric peers endorse transactions by executing a smart contract using the
inputs of the transaction proposal. The peers then sign the input and output
generated by the smart contract execution. The endorsement policy specifies the
set of organizations whose peers need to endorse a transaction before it can be
added to the ledger.
Each chaincode that is deployed to a channel has an endorsement policy that governs
the assets managed by the chaincode smart contracts. However, you can override the
chaincode level endorsement policy to create an endorsement policy for a specific key,
either on the public channel ledger or in a private collection.
State-based endorsement policies, also known as key-level endorsement policies, allow
channel members to use different endorsement policies for assets that are managed by
the same smart contract. For more information about endorsement policies and
state-based endorsement, visit the
[Endorsement Policies](https://hyperledger-fabric.readthedocs.io/en/release-2.2/endorsement-policies.html)
topic in the Fabric documentation.
The implementation provided by State Based interface creates a policy which requires signatures from all the Org principals added, and hence is equivalent to an AND policy. For other advanced State Based policy implementations which are not supported by State Based interface directly like OR or NOutOf policies, please refer to method implementations setStateBasedEndorsementNOutOf(), which can be used as an alternative for setStateBasedEndorsement() inside asset-transfer-sbe smart contracts.
## About the Sample
The state-based endorsement (SBE) asset transfer sample demonstrates how to use
key-level endorsement policies to ensure that an asset only is endorsed by an
asset owner. In the course of the tutorial, you will use the smart contract
to complete the following transfer scenario:
- Deploy the SBE smart contract to a channel that was created using the Fabric
test network. The channel will have two members, Org1 and Org2, that will
participate in the asset transfer. Each organization operates one peer that
is joined to the channel.
- Create an asset using the chaincode endorsement policy. The chaincode level
endorsement policy requires that a majority of organizations on the channel
endorse a transaction. This means that a transaction that creates an asset
needs to be endorsed by peers that belong to Org1 and Org2. When the asset is
created, the smart contract creates a state-based endorsement policy that
specifies that only the organization that owns that asset may endorse update
transactions. Because the asset is owned by Org1, any future updates to the asset
need to be endorsed by the Org1 peer.
- Update the asset by only endorsing with Org1, this will use the state-based
endorsement policy applied to the asset when it was created by the chaincode.
- Transfer the asset to Org2. During the execution of the transfer transaction,
the chaincode will create a new state-based endorsement policy that reflects
the new asset owner for the asset.
- Update the asset once more, this time with Org2 as the owner. Because the
state-based endorsement policy has been updated, this transaction only needs
to be endorsed by Org2.
## Deploy the smart contract
We are going to run the SBE smart contract using the Fabric test network. Open a command terminal and navigate to test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
```
cd fabric-samples/test-network
```
Run the following command to deploy the test network and create a channel named `mychannel`:
```
./network.sh up createChannel
```
You can use the test network script to deploy the smart contract to the channel that was just created. The script uses the Fabric chaincode lifecycle to deploy the smart contract to the channel. We will use the default chaincode level endorsement policy used by the Fabric chaincode lifecycle, which requires an endorsement from a majority of channel members. In our case, this will require that both Org1 and Org2 endorse a transaction (2 of 2). Deploy the smart contract to `mychannel` using the following command:
```
./network.sh deployCC -ccn sbe -ccp ../asset-transfer-sbe/chaincode-typescript/ -ccl typescript
```
Set the following environment variables to interact with the network as a user from Org1:
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
```
## Run the transfer scenario
We can now invoke the SBE smart contract to create a new asset:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"CreateAsset","Args":["asset1","100","Org1User1"]}'
```
The create transaction needs to target both peers from Org1 and Org2 to meet the chaincode endorsement policy. The chaincode will read the MSP ID of the client user submitting the transaction and assign that organization as the asset owner. As a result, the asset will initially be owned by Org1.
You can query the asset using with the following command:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The result is a new asset owned by Org1, identified using the Org1 MSP ID `Org1MSP`:
`{"ID":"asset1","Value":100,"Owner":"Org1User1","OwnerOrg":"Org1MSP"}`
In addition to creating the asset, the `CreateAsset` function also sets a state-based endorsement policy for the asset. Only a peer of the asset owner, can successfully endorse an asset update. To demonstrate the key-level endorsement policy, lets try to update the asset while targeting the Org2 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","200"]}'
```
The result is an endorsement policy failure:
```
Error: transaction invalidated with status (ENDORSEMENT_POLICY_FAILURE) - proposal response: <nil>
```
If we attempt to update the asset with an endorsement from the Org1 peer, the update succeeds:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","200"]}'
```
You can query the asset one more time to verify that the update was successful:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The asset value is now 200:
```
{"ID":"asset1","Value":200,"Owner":"Org1User1","OwnerOrg":"Org1MSP"}
```
Now that we have tested the asset key-level endorsement policy, we can transfer the asset to Org2. Run the following command to transfer the asset from Org1 to Org2. This time the Org2 MSP ID is provided as a transaction input. The `TransferAsset` function will update the endorsement policy to specify that only a peer of the new owner can update the asset. Note that this command targets the Org1 peer.
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"TransferAsset","Args":["asset1","Org2User1","Org2MSP"]}'
```
We can query the asset to see that the owner has been updated from Org1 to Org2:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The owning organization is now Org2:
```
{"ID":"asset1","Value":200,"Owner":"Org2User1","OwnerOrg":"Org2MSP"}
```
Org2 now needs to endorse any asset updates. Run the following command to try to update the asset with an endorsement from the Org1 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","300"]}'
```
The response will be an endorsement policy failure:
```
Error: transaction invalidated with status (ENDORSEMENT_POLICY_FAILURE) - proposal response: <nil>
```
Now try to update the asset with an endorsement from the Org2 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","300"]}'
```
You can query the asset again to verify that the transaction update succeeded:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The asset value is now 300:
```
{"ID":"asset1","Value":300,"Owner":"Org2User1","OwnerOrg":"Org2MSP"}
```
Note that the transaction to update the asset was submitted by a user from Org1, even though the asset was owned by Org2. The transfer enabled by the SBE smart contract is a simple scenario meant only to demonstrate the use of state-based endorsement policies. The smart contract can use access control to specify that an asset can only be updated by its owner. Private data collections can also be used to ensure that transfers need to be endorsed by the owner and recipient of the transfer, instead of just the asset owner. For a more realistic example of an asset transfer scenario, see the [Secured asset transfer in Fabric](https://hyperledger-fabric.readthedocs.io/en/master/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) tutorial.
## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
```
./network.sh down
```

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,37 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -1,14 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
package-lock.json
wallet
!wallet/.gitkeep

View file

@ -1,356 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
/**
* A test application to show state based endorsements operations with a running
* asset-transfer-sbe chaincode with discovery.
* -- How to submit a transaction
* -- How to query
* -- How to limit the organizations involved in a transaction
*
* To see the SDK workings, try setting the logging to show on the console before running
* export HFC_LOGGING='{"debug":"console"}'
*/
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities
// ===> from directory /fabric-samples/test-network
// ./network.sh up createChannel -ca
// - Use any of the asset-transfer-sbe chaincodes deployed on the channel "mychannel"
// with the chaincode name of "sbe". The following deploy command will package,
// install, approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory /fabric-samples/test-network
// ./network.sh deployCC -ccn sbe -ccp ../asset-transfer-sbe/chaincode-typescript/ -ccl typescript
// - Be sure that node.js is installed
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
// npm install
// - to run this test application
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
// node app.js
// NOTE: If you see an error like these:
/*
Error in setup: Error: DiscoveryService: mychannel error: access denied
OR
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
*/
// Delete the /fabric-samples/asset-transfer-sbe/application-javascript/wallet directory
// and retry this application.
//
// The certificate authority must have been restarted and the saved certificates for the
// admin and application user are not valid. Deleting the wallet store will force these to be reset
// with the new certificate authority.
//
const { Gateway, Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const channelName = 'mychannel';
const chaincodeName = 'sbe';
const org1 = 'Org1MSP';
const org2 = 'Org2MSP';
const Org1UserId = 'appUser1';
const Org2UserId = 'appUser2';
async function initGatewayForOrg1() {
console.log('\n--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer');
// build an in memory object with the network configuration (also known as a connection profile)
const ccpOrg1 = buildCCPOrg1();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
// setup the wallet to cache the credentials of the application user, on the app server locally
const walletPathOrg1 = path.join(__dirname, 'wallet', 'org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
// in a real application this would be done on an administrative flow, and only once
// stores admin identity in local wallet, if needed
await enrollAdmin(caOrg1Client, walletOrg1, org1);
// register & enroll application user with CA, which is used as client identify to make chaincode calls
// and stores app user identity in local wallet
// In a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caOrg1Client, walletOrg1, org1, Org1UserId, 'org1.department1');
try {
// Create a new gateway for connecting to Org's peer node.
const gatewayOrg1 = new Gateway();
//connect using Discovery enabled
await gatewayOrg1.connect(ccpOrg1,
{ wallet: walletOrg1, identity: Org1UserId, discovery: { enabled: true, asLocalhost: true } });
return gatewayOrg1;
} catch (error) {
console.error(`Error in connecting to gateway for Org1: ${error}`);
process.exit(1);
}
}
async function initGatewayForOrg2() {
console.log('\n--> Fabric client user & Gateway init: Using Org2 identity to Org2 Peer');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet', 'org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await enrollAdmin(caOrg2Client, walletOrg2, org2);
await registerAndEnrollUser(caOrg2Client, walletOrg2, org2, Org2UserId, 'org2.department1');
try {
// Create a new gateway for connecting to Org's peer node.
const gatewayOrg2 = new Gateway();
await gatewayOrg2.connect(ccpOrg2,
{ wallet: walletOrg2, identity: Org2UserId, discovery: { enabled: true, asLocalhost: true } });
return gatewayOrg2;
} catch (error) {
console.error(`Error in connecting to gateway for Org2: ${error}`);
process.exit(1);
}
}
function checkAsset(org, assetKey, resultBuffer, value, ownerOrg) {
let asset;
if (resultBuffer) {
asset = JSON.parse(resultBuffer.toString('utf8'));
}
if (asset && value) {
if (asset.Value === value && asset.OwnerOrg === ownerOrg) {
console.log(`*** Result from ${org} - asset ${asset.ID} has value of ${asset.Value} and owned by ${asset.OwnerOrg}`);
} else {
console.log(`*** Failed from ${org} - asset ${asset.ID} has value of ${asset.Value} and owned by ${asset.OwnerOrg}`);
}
} else if (!asset && value === 0 ) {
console.log(`*** Success from ${org} - asset ${assetKey} does not exist`);
} else {
console.log('*** Failed - asset read failed');
}
}
async function readAssetByBothOrgs(assetKey, value, ownerOrg, contractOrg1, contractOrg2) {
if (value) {
console.log(`\n--> Evaluate Transaction: ReadAsset, - ${assetKey} should have a value of ${value} and owned by ${ownerOrg}`);
} else {
console.log(`\n--> Evaluate Transaction: ReadAsset, - ${assetKey} should not exist`);
}
let resultBuffer;
resultBuffer = await contractOrg1.evaluateTransaction('ReadAsset', assetKey);
checkAsset('Org1', assetKey, resultBuffer, value, ownerOrg);
resultBuffer = await contractOrg2.evaluateTransaction('ReadAsset', assetKey);
checkAsset('Org2', assetKey, resultBuffer, value, ownerOrg);
}
// This application uses fabric-samples/test-network based setup and the companion chaincode
// For this illustration, both Org1 & Org2 client identities will be used, however
// notice they are used by two different "gateway"s to simulate two different running
// applications from two different organizations.
async function main() {
try {
// use a random key so that we can run multiple times
const assetKey = `asset-${Math.floor(Math.random() * 100) + 1}`;
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */
const gatewayOrg1 = await initGatewayForOrg1();
const networkOrg1 = await gatewayOrg1.getNetwork(channelName);
const contractOrg1 = networkOrg1.getContract(chaincodeName);
/** ******* Fabric client init: Using Org2 identity to Org2 Peer ******* */
const gatewayOrg2 = await initGatewayForOrg2();
const networkOrg2 = await gatewayOrg2.getNetwork(channelName);
const contractOrg2 = networkOrg2.getContract(chaincodeName);
try {
let transaction;
try {
// Create an asset by organization Org1, this will require that both organization endorse.
// The endorsement will be handled by Discovery, since the gateway was connected with discovery enabled.
console.log(`\n--> Submit Transaction: CreateAsset, ${assetKey} as Org1 - endorsed by Org1 and Org2`);
await contractOrg1.submitTransaction('CreateAsset', assetKey, '100', 'Tom');
console.log('*** Result: committed, now asset will only require Org1 to endorse');
} catch (createError) {
console.log(`*** Failed: create - ${createError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 100, org1, contractOrg1, contractOrg2);
try {
// Since the gateway is using discovery we should limit the organizations used by
// discovery to endorse. This way we only have to know the organization and not
// the actual peers that may be active at any given time.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1`);
transaction = contractOrg1.createTransaction('UpdateAsset');
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey, '200');
console.log('*** Result: committed');
} catch (updateError) {
console.log(`*** Failed: update - ${updateError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 200, org1, contractOrg1, contractOrg2);
try {
// Submit a transaction to make an update to the asset that has a key-level endorsement policy
// set to only allow Org1 to make updates. The following example will not use the "setEndorsingOrganizations"
// to limit the organizations that will do the endorsement, this means that it will be sent to all
// organizations in the chaincode endorsement policy. When Org1 endorses, the transaction will be committed
// if Org2 endorses or not.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1 and Org2`);
transaction = contractOrg1.createTransaction('UpdateAsset');
await transaction.submit(assetKey, '300');
console.log('*** Result: committed - because Org1 and Org2 both endorsed, while only the Org1 endorsement was required and checked');
} catch (updateError) {
console.log(`*** Failed: update - ${updateError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 300, org1, contractOrg1, contractOrg2);
try {
// Again submit the change to both Organizations by not using "setEndorsingOrganizations". Since only
// Org1 is required to approve, the transaction will be committed.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org1 and Org2`);
transaction = contractOrg2.createTransaction('UpdateAsset');
await transaction.submit(assetKey, '400');
console.log('*** Result: committed - because Org1 was on the discovery list, Org2 did not endorse');
} catch (updateError) {
console.log(`*** Failed: update - ${updateError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 400, org1, contractOrg1, contractOrg2);
try {
// Try to update by sending only to Org2, since the state-based-endorsement says that
// Org1 is the only organization allowed to update, the transaction will fail.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org2`);
transaction = contractOrg2.createTransaction('UpdateAsset');
transaction.setEndorsingOrganizations(org2);
await transaction.submit(assetKey, '500');
console.log('*** Failed: committed - this should have failed to endorse and commit');
} catch (updateError) {
console.log(`*** Successfully caught the error: \n ${updateError}`);
}
await readAssetByBothOrgs(assetKey, 400, org1, contractOrg1, contractOrg2);
try {
// Make a change to the state-based-endorsement policy making Org2 the owner.
console.log(`\n--> Submit Transaction: TransferAsset ${assetKey}, as Org1 - endorse by Org1`);
transaction = contractOrg1.createTransaction('TransferAsset');
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey, 'Henry', org2);
console.log('*** Result: committed');
} catch (transferError) {
console.log(`*** Failed: transfer - ${transferError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 400, org2, contractOrg1, contractOrg2);
try {
// Make sure that Org2 can now make updates, notice how the transaction has limited the
// endorsement to only Org2.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org2`);
transaction = contractOrg2.createTransaction('UpdateAsset');
transaction.setEndorsingOrganizations(org2);
await transaction.submit(assetKey, '600');
console.log('*** Result: committed');
} catch (updateError) {
console.log(`*** Failed: update - ${updateError}`);
process.exit(1);
}
await readAssetByBothOrgs(assetKey, 600, org2, contractOrg1, contractOrg2);
try {
// With Org2 now the owner and the state-based-endorsement policy only allowing organization Org2
// to make updates, a transaction only to Org1 will fail.
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1`);
transaction = contractOrg1.createTransaction('UpdateAsset');
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey, '700');
console.log('*** Failed: committed - this should have failed to endorse and commit');
} catch (updateError) {
console.log(`*** Successfully caught the error: \n ${updateError}`);
}
await readAssetByBothOrgs(assetKey, 600, org2, contractOrg1, contractOrg2);
try {
// With Org2 the owner and the state-based-endorsement policy only allowing organization Org2
// to make updates, a transaction to delete by Org1 will fail.
console.log(`\n--> Submit Transaction: DeleteAsset ${assetKey}, as Org1 - endorse by Org1`);
transaction = contractOrg1.createTransaction('DeleteAsset');
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey);
console.log('*** Failed: committed - this should have failed to endorse and commit');
} catch (updateError) {
console.log(`*** Successfully caught the error: \n ${updateError}`);
}
try {
// With Org2 the owner and the state-based-endorsement policy only allowing organization Org2
// to make updates, a transaction to delete by Org2 will succeed.
console.log(`\n--> Submit Transaction: DeleteAsset ${assetKey}, as Org2 - endorse by Org2`);
transaction = contractOrg2.createTransaction('DeleteAsset');
transaction.setEndorsingOrganizations(org2);
await transaction.submit(assetKey);
console.log('*** Result: committed');
} catch (deleteError) {
console.log(`*** Failed: delete - ${deleteError}`);
process.exit(1);
}
// The asset should now be deleted, both orgs should not be able to read it
try {
await readAssetByBothOrgs(assetKey, 0, org2, contractOrg1, contractOrg2);
} catch (readDeleteError) {
console.log(`*** Successfully caught the error: ${readDeleteError}`);
}
} catch (runError) {
console.error(`Error in transaction: ${runError}`);
if (runError.stack) {
console.error(runError.stack);
}
process.exit(1);
} finally {
// Disconnect from the gateway peer when all work for this client identity is complete
gatewayOrg1.disconnect();
gatewayOrg2.disconnect();
}
} catch (error) {
console.error(`Error in setup: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,22 +0,0 @@
{
"name": "asset-transfer-sbe",
"version": "1.0.0",
"description": "Asset transfer state based endorsement application implemented in JavaScript",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"scripts": {
"lint": "eslint *.js"
},
"dependencies": {
"fabric-ca-client": "^2.2.19",
"fabric-network": "^2.2.19"
},
"devDependencies": {
"eslint": "^7.32.0"
}
}

View file

@ -1,6 +0,0 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View file

@ -1,10 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
/.classpath
/.gradle/
/.project
/.settings/
/bin/
/build/

View file

@ -1,86 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'application'
id 'checkstyle'
id 'jacoco'
}
group 'org.hyperledger.fabric.samples'
version '1.0-SNAPSHOT'
dependencies {
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.5.+'
implementation 'org.hyperledger.fabric:fabric-protos:0.3.3'
implementation 'com.owlike:genson:1.6'
}
repositories {
mavenCentral()
maven {
url 'https://jitpack.io'
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
application {
mainClass = 'org.hyperledger.fabric.contract.ContractRouter'
}
checkstyle {
toolVersion '8.21'
configFile file("config/checkstyle/checkstyle.xml")
}
checkstyleMain {
source ='src/main/java'
}
checkstyleTest {
source ='src/test/java'
}
jacocoTestReport {
dependsOn test
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.9
}
}
}
finalizedBy jacocoTestReport
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
shadowJar {
archiveBaseName = 'chaincode'
archiveVersion = ''
archiveClassifier = ''
mergeServiceFiles()
manifest {
attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
}
}
check.dependsOn jacocoTestCoverageVerification
installDist.dependsOn check

View file

@ -1,172 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that matches the Eclipse formatter
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sourceforge.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="fileExtensions" value="java, properties, xml"/>
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
<property name="optional" value="false"/>
</module>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
<!-- <module name="JavadocPackage"/> -->
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
<!-- <module name="JavadocMethod"/> -->
<!-- <module name="JavadocType"/> -->
<!-- <module name="JavadocVariable"/> -->
<!-- <module name="JavadocStyle"/> -->
<!-- <module name="MissingJavadocMethod"/> -->
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="PackageName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField">
<property name="ignoreConstructorParameter" value="true"/>
<property name="ignoreSetter" value="true"/>
</module>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
<module name="DesignForExtension"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier">
<property name="allowPublicFinalFields" value="true"/>
</module>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>

View file

@ -1,9 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="ChaincodeTest.java" checks="ParameterNumber" />
</suppressions>

View file

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
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
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# 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"'
# 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 \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,92 +0,0 @@
@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=.
@rem This is normally unused
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% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 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!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,5 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
rootProject.name = 'sbe'

View file

@ -1,96 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.sbe;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
import java.util.Objects;
@DataType()
public final class Asset {
@Property()
private final String ID;
@Property()
private int Value;
@Property()
private String Owner;
@Property()
private String OwnerOrg;
@JsonProperty("ID")
public String getID() {
return ID;
}
@JsonProperty("Value")
public int getValue() {
return Value;
}
public void setValue(final int Value) {
this.Value = Value;
}
@JsonProperty("Owner")
public String getOwner() {
return Owner;
}
public void setOwner(final String Owner) {
this.Owner = Owner;
}
@JsonProperty("OwnerOrg")
public String getOwnerOrg() {
return OwnerOrg;
}
public void setOwnerOrg(final String OwnerOrg) {
this.OwnerOrg = OwnerOrg;
}
public Asset(@JsonProperty("ID") final String ID, @JsonProperty("Value") final int Value,
@JsonProperty("Owner") final String Owner, @JsonProperty("OwnerOrg") final String OwnerOrg) {
this.ID = ID;
this.Value = Value;
this.Owner = Owner;
this.OwnerOrg = OwnerOrg;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Asset asset = (Asset) o;
return getValue() == asset.getValue()
&&
getID().equals(asset.getID())
&&
getOwner().equals(asset.getOwner())
&&
getOwnerOrg().equals(asset.getOwnerOrg());
}
@Override
public int hashCode() {
return Objects.hash(getID(), getValue(), getOwner(), getOwnerOrg());
}
@Override
public String toString() {
return "Asset{" + "ID='" + ID + '\'' + ", Value=" + Value + ", Owner='"
+ Owner + '\'' + ", OwnerOrg='" + OwnerOrg + '\'' + '}';
}
}

View file

@ -1,269 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.sbe;
import com.owlike.genson.Genson;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.protos.common.MSPPrincipal;
import org.hyperledger.fabric.protos.common.MSPRole;
import org.hyperledger.fabric.protos.common.SignaturePolicy;
import org.hyperledger.fabric.protos.common.SignaturePolicyEnvelope;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ext.sbe.StateBasedEndorsement;
import org.hyperledger.fabric.shim.ext.sbe.impl.StateBasedEndorsementFactory;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Contract(
name = "sbe",
info = @Info(
title = "Asset Contract",
description = "Asset Transfer Smart Contract, using State Based Endorsement(SBE), implemented in Java",
version = "0.0.1-SNAPSHOT",
license = @License(
name = "Apache 2.0 License",
url = "http://www.apache.org/licenses/LICENSE-2.0.html")))
@Default
public final class AssetContract implements ContractInterface {
private final Genson genson = new Genson();
private enum AssetTransferErrors {
ASSET_NOT_FOUND,
ASSET_ALREADY_EXISTS
}
/**
* Creates a new asset.
* Sets the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates.
* Optionally, set the endorsement policy of the assetId Key, such that any 1(N) out of the Org's specified can endorse future updates.
*
* @param ctx the transaction context
* @param assetId the id of the new asset
* @param value the value of the new asset
* @param owner the owner of the new asset
* @return the created asset
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset CreateAsset(final Context ctx, final String assetId, final int value, final String owner) {
ChaincodeStub stub = ctx.getStub();
if (AssetExists(ctx, assetId)) {
String errorMessage = String.format("Asset %s already exists", assetId);
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_ALREADY_EXISTS.toString());
}
final String ownerOrg = getClientOrgId(ctx);
Asset asset = new Asset(assetId, value, owner, ownerOrg);
String assetJSON = genson.serialize(asset);
stub.putStringState(assetId, assetJSON);
// Set the endorsement policy of the assetId Key, such that current owner Org is required to endorse future updates
setStateBasedEndorsement(ctx, assetId, List.of(ownerOrg));
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// setStateBasedEndorsementNOutOf(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"});
return asset;
}
/**
* Retrieves an asset with the given assetId.
*
* @param ctx the transaction context
* @param assetId the id of the asset
* @return the asset found on the ledger if there was one
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String ReadAsset(final Context ctx, final String assetId) {
ChaincodeStub stub = ctx.getStub();
String assetJSON = stub.getStringState(assetId);
if (assetJSON == null || assetJSON.isEmpty()) {
String errorMessage = String.format("Asset %s does not exist", assetId);
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
}
return assetJSON;
}
/**
* Updates the properties of an existing asset.
* Needs an endorsement of current owner Org Peer.
*
* @param ctx the transaction context
* @param assetId the id of the asset being updated
* @param newValue the value of the asset being updated
* @return the updated asset
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset UpdateAsset(final Context ctx, final String assetId, final int newValue) {
ChaincodeStub stub = ctx.getStub();
String assetString = ReadAsset(ctx, assetId);
Asset asset = genson.deserialize(assetString, Asset.class);
asset.setValue(newValue);
String updatedAssetJSON = genson.serialize(asset);
stub.putStringState(assetId, updatedAssetJSON);
return asset;
}
/**
* Deletes the given asset.
* Needs an endorsement of current owner Org Peer.
*
* @param ctx the transaction context
* @param assetId the id of the asset being deleted
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void DeleteAsset(final Context ctx, final String assetId) {
ChaincodeStub stub = ctx.getStub();
if (!AssetExists(ctx, assetId)) {
String errorMessage = String.format("Asset %s does not exist", assetId);
System.out.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
}
stub.delState(assetId);
}
/**
* Updates the owner & ownerOrg field of asset with given assetId, ownerOrg must be a valid Org MSP Id.
* Needs an endorsement of current owner Org Peer.
* Re-sets the endorsement policy of the assetId Key, such that new owner Org Peer is required to endorse future updates.
*
* @param ctx the transaction context
* @param assetId the id of the asset being transferred
* @param newOwner the new owner
* @param newOwnerOrg the new owner Org MSPID
* @return the updated asset
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset TransferAsset(final Context ctx, final String assetId, final String newOwner, final String newOwnerOrg) {
ChaincodeStub stub = ctx.getStub();
String assetString = ReadAsset(ctx, assetId);
Asset asset = genson.deserialize(assetString, Asset.class);
asset.setOwner(newOwner);
asset.setOwnerOrg(newOwnerOrg);
String updatedAssetJSON = genson.serialize(asset);
stub.putStringState(assetId, updatedAssetJSON);
// Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates
setStateBasedEndorsement(ctx, assetId, List.of(newOwnerOrg));
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// setStateBasedEndorsementNOutOf(ctx, assetId, 1, List.of("Org1MSP", "Org2MSP"));
return asset;
}
/**
* Checks the existence of the asset.
*
* @param ctx the transaction context
* @param assetId the id of the asset
* @return boolean indicating the existence of the asset
*/
private boolean AssetExists(final Context ctx, final String assetId) {
ChaincodeStub stub = ctx.getStub();
String assetJSON = stub.getStringState(assetId);
return (assetJSON != null && !assetJSON.isEmpty());
}
/**
* Retrieves the client's OrgId (MSPID)
*
* @param ctx the transaction context
* @return String value of the Org MSPID
*/
private static String getClientOrgId(final Context ctx) {
return ctx.getClientIdentity().getMSPID();
}
/**
* Sets an endorsement policy to the assetId Key.
* Enforces that the owner Org must endorse future update transactions for the specified assetId Key.
*
* @param ctx the transaction context
* @param assetId the id of the asset
* @param ownerOrgs the list of Owner Org MSPID's
*/
private static void setStateBasedEndorsement(final Context ctx, final String assetId, final List<String> ownerOrgs) {
StateBasedEndorsement stateBasedEndorsement = StateBasedEndorsementFactory.getInstance().newStateBasedEndorsement(null);
stateBasedEndorsement.addOrgs(StateBasedEndorsement.RoleType.RoleTypeMember, ownerOrgs.toArray(new String[0]));
ctx.getStub().setStateValidationParameter(assetId, stateBasedEndorsement.policy());
}
/**
* Sets an endorsement policy to the assetId Key.
* Enforces that a given number of Orgs (N) out of the specified Orgs must endorse future update transactions for the specified assetId Key.
*
* @param ctx the transaction context
* @param assetId the id of the asset
* @param nOrgs the number of N Orgs to endorse out of the list of Orgs provided
* @param ownerOrgs the list of Owner Org MSPID's
*/
private static void setStateBasedEndorsementNOutOf(final Context ctx, final String assetId, final int nOrgs, final List<String> ownerOrgs) {
ctx.getStub().setStateValidationParameter(assetId, policy(nOrgs, ownerOrgs));
}
/**
* Create a policy that requires a given number (N) of Org principals signatures out of the provided list of Orgs
*
* @param nOrgs the number of Org principals signatures required to endorse (out of the provided list of Orgs)
* @param mspIds the list of Owner Org MSPID's
*/
private static byte[] policy(final int nOrgs, final List<String> mspIds) {
mspIds.sort(Comparator.naturalOrder());
var principals = mspIds.stream()
.map(mspId -> MSPRole.newBuilder()
.setMspIdentifier(mspId)
.setRole(MSPRole.MSPRoleType.MEMBER)
.build())
.map(role -> MSPPrincipal.newBuilder()
.setPrincipalClassification(MSPPrincipal.Classification.ROLE)
.setPrincipal(role.toByteString())
.build())
.collect(Collectors.toList());
var signPolicy = IntStream.range(0, mspIds.size())
.mapToObj(AssetContract::signedBy)
.collect(Collectors.toList());
// Create the policy such that it requires any N signature's from all the principals provided
return SignaturePolicyEnvelope.newBuilder()
.setVersion(0)
.setRule(nOutOf(nOrgs, signPolicy))
.addAllIdentities(principals)
.build()
.toByteArray();
}
private static SignaturePolicy signedBy(final int index) {
return SignaturePolicy.newBuilder().setSignedBy(index).build();
}
private static SignaturePolicy nOutOf(final int n, final List<SignaturePolicy> policies) {
return SignaturePolicy.newBuilder().setNOutOf(
SignaturePolicy.NOutOf.newBuilder().setN(n).addAllRules(policies).build()
).build();
}
}

View file

@ -1,20 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
package-lock.json
# Compiled TypeScript files
dist
# Editor Config
.editorconfig
# npm ignore
.npmignore

View file

@ -1,13 +0,0 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -1,35 +0,0 @@
{
"name": "asset-transfer-sbe",
"version": "0.0.1",
"description": "Asset Transfer contract, using State Based Endorsement(SBE), implemented in TypeScript",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint src",
"pretest": "npm run lint",
"test": "echo 'No tests implemented'",
"start": "fabric-chaincode-node start",
"build": "tsc",
"build:watch": "tsc -w",
"prepublishOnly": "npm run build",
"postinstall": "npm dedupe"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "~2.5",
"fabric-shim": "~2.5"
},
"devDependencies": {
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
}
}

View file

@ -1,20 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import { Object, Property } from 'fabric-contract-api';
@Object()
export class Asset {
@Property()
public ID: string = '';
@Property()
public Value: number = 0;
@Property()
public Owner: string = '';
@Property()
public OwnerOrg: string = '';
}

View file

@ -1,121 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import { Context, Contract, Info, Transaction } from 'fabric-contract-api';
import { Asset } from './asset';
import { KeyEndorsementPolicy } from 'fabric-shim';
@Info({title: 'AssetContract', description: 'Asset Transfer Smart Contract, using State Based Endorsement(SBE), implemented in TypeScript' })
export class AssetContract extends Contract {
// CreateAsset creates a new asset
// CreateAsset sets the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates
@Transaction()
public async CreateAsset(ctx: Context, assetId: string, value: number, owner: string): Promise<void> {
const exists = await this.AssetExists(ctx, assetId);
if (exists) {
throw new Error(`The asset ${assetId} already exists`);
}
const ownerOrg = AssetContract.getClientOrgId(ctx);
const asset = new Asset();
asset.ID = assetId;
asset.Value = value;
asset.Owner = owner;
asset.OwnerOrg = ownerOrg;
const buffer = Buffer.from(JSON.stringify(asset));
// Create the asset
await ctx.stub.putState(assetId, buffer);
// Set the endorsement policy of the assetId Key, such that current owner Org is required to endorse future updates
await AssetContract.setStateBasedEndorsement(ctx, assetId, [ownerOrg]);
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// await AssetContract.setStateBasedEndorsementNOutOf(ctx, assetId, 1, ["Org1MSP", "Org2MSP"]);
}
// ReadAsset returns asset with given assetId
@Transaction(false)
public async ReadAsset(ctx: Context, assetId: string): Promise<string> {
const exists = await this.AssetExists(ctx, assetId);
if (!exists) {
throw new Error(`The asset ${assetId} does not exist`);
}
// Read the asset
const assetJSON = await ctx.stub.getState(assetId);
return assetJSON.toString();
}
// UpdateAsset updates an existing asset
// UpdateAsset needs an endorsement of current owner Org Peer
@Transaction()
public async UpdateAsset(ctx: Context, assetId: string, newValue: number): Promise<void> {
const assetString = await this.ReadAsset(ctx, assetId);
const asset = JSON.parse(assetString) as Asset;
asset.Value = newValue;
const buffer = Buffer.from(JSON.stringify(asset));
// Update the asset
await ctx.stub.putState(assetId, buffer);
}
// DeleteAsset deletes an given asset
// DeleteAsset needs an endorsement of current owner Org Peer
@Transaction()
public async DeleteAsset(ctx: Context, assetId: string): Promise<void> {
const exists = await this.AssetExists(ctx, assetId);
if (!exists) {
throw new Error(`The asset ${assetId} does not exist`);
}
// Delete the asset
await ctx.stub.deleteState(assetId);
}
// TransferAsset updates the Owner & OwnerOrg field of asset with given assetId, OwnerOrg must be a valid Org MSP Id
// TransferAsset needs an endorsement of current owner Org Peer
// TransferAsset re-sets the endorsement policy of the assetId Key, such that new owner Org Peer is required to endorse future updates
@Transaction()
public async TransferAsset(ctx: Context, assetId: string, newOwner: string, newOwnerOrg: string): Promise<void> {
const assetString = await this.ReadAsset(ctx, assetId);
const asset = JSON.parse(assetString) as Asset;
asset.Owner = newOwner;
asset.OwnerOrg = newOwnerOrg;
// Update the asset
await ctx.stub.putState(assetId, Buffer.from(JSON.stringify(asset)));
// Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates
await AssetContract.setStateBasedEndorsement(ctx, asset.ID, [newOwnerOrg]);
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// await AssetContract.setStateBasedEndorsementNOutOf(ctx, assetId, 1, ["Org1MSP", "Org2MSP"]);
}
// AssetExists returns true when asset with given ID exists
public async AssetExists(ctx: Context, assetId: string): Promise<boolean> {
const buffer = await ctx.stub.getState(assetId);
return buffer.length > 0;
}
// getClientOrgId gets the client's OrgId (MSPID)
private static getClientOrgId(ctx: Context): string {
return ctx.clientIdentity.getMSPID();
}
// setStateBasedEndorsement sets an endorsement policy to the assetId Key
// setStateBasedEndorsement enforces that the owner Org must endorse future update transactions for the specified assetId Key
private static async setStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise<void> {
const ep = new KeyEndorsementPolicy();
ep.addOrgs('MEMBER', ...ownerOrgs);
await ctx.stub.setStateValidationParameter(assetId, ep.getPolicy());
}
// setStateBasedEndorsementNOutOf sets an endorsement policy to the assetId Key
// setStateBasedEndorsementNOutOf enforces that a given number of Orgs (N) out of the specified Orgs must endorse future update transactions for the specified assetId Key.
private static async setStateBasedEndorsementNOutOf(ctx: Context, assetId: string, nOrgs: number, ownerOrgs: string[]): Promise<void> {
const ROLE_TYPE_MEMBER = 'MEMBER';
// Use the KeyEndorsementPolicy helper form the chaincode libarries
// If you need more advanced policies, please use that helper as a reference point.
const keyEndorsementPolicy = new KeyEndorsementPolicy();
keyEndorsementPolicy.addOrgs(ROLE_TYPE_MEMBER, ...ownerOrgs);
await ctx.stub.setStateValidationParameter(assetId, keyEndorsementPolicy.getPolicy());
}
}

View file

@ -1,8 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import { AssetContract } from './assetContract';
export { AssetContract } from './assetContract';
export const contracts: unknown[] = [ AssetContract ];

View file

@ -1,17 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/"]
}

View file

@ -1,61 +0,0 @@
# Asset transfer secured agreement sample
The asset transfer events sample demonstrates how to transfer a private asset between two organizations without publicly sharing data .
## About the sample
This sample includes smart contract and application code in multiple languages. This sample shows how Fabric features state based endorsement, private data, and access control to provide secured transactions.
### Application
Refer [Secured asset transfer in Fabric](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) for application details .
### Smart Contract
The smart contract (in folder `chaincode-go`) implements the following functions to support the application:
- CreateAsset
- ChangePublicDescription
- AgreeToSell
- AgreeToBuy
- VerifyAssetProperties
- TransferAsset
- ReadAsset
- GetAssetPrivateProperties
- GetAssetSalesPrice
- GetAssetBidPrice
- GetAssetHashId
- QueryAssetSaleAgreements
- QueryAssetBuyAgreements
- QueryAssetHistory
## Running the sample
Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
1. Deploy the smart contract implementations.
```
# To deploy the go chaincode implementation
./network.sh deployCC -ccn secured -ccp ../asset-transfer-secured-agreement/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
1. Run the application (from the `asset-transfer-secured-agreement` folder).
```
# To run the Typescript sample application
cd application-gateway-typescript
npm install
npm start
```
## Clean up
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down
```

View file

@ -1,14 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

View file

@ -1,13 +0,0 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

View file

@ -1,33 +0,0 @@
{
"name": "asset-transfer-basic",
"version": "1.0.0",
"description": "Asset Transfer Secured Agreement Application implemented in typeScript using fabric-gateway",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=18"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"lint": "eslint src",
"prepare": "npm run build",
"pretest": "npm run lint",
"start": "node dist/app.js"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.10",
"@hyperledger/fabric-gateway": "^1.5"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.18.6",
"eslint": "^8.57.0",
"typescript": "~5.4",
"typescript-eslint": "^7.13.0"
}
}

View file

@ -1,215 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { connect } from '@hyperledger/fabric-gateway';
import { newGrpcConnection, newIdentity, newSigner, tlsCertPathOrg1, peerEndpointOrg1, peerNameOrg1, certDirectoryPathOrg1, mspIdOrg1, keyDirectoryPathOrg1, tlsCertPathOrg2, peerEndpointOrg2, peerNameOrg2, certDirectoryPathOrg2, mspIdOrg2, keyDirectoryPathOrg2 } from './connect';
import { ContractWrapper } from './contractWrapper';
import { RED, RESET } from './utils';
const channelName = 'mychannel';
const chaincodeName = 'secured';
// Use a random key so that we can run multiple times
const now = Date.now().toString();
let assetKey: string;
async function main(): Promise<void> {
// The gRPC client connection from org1 should be shared by all Gateway connections to this endpoint.
const clientOrg1 = await newGrpcConnection(
tlsCertPathOrg1,
peerEndpointOrg1,
peerNameOrg1
);
const gatewayOrg1 = connect({
client: clientOrg1,
identity: await newIdentity(certDirectoryPathOrg1, mspIdOrg1),
signer: await newSigner(keyDirectoryPathOrg1),
});
// The gRPC client connection from org2 should be shared by all Gateway connections to this endpoint.
const clientOrg2 = await newGrpcConnection(
tlsCertPathOrg2,
peerEndpointOrg2,
peerNameOrg2
);
const gatewayOrg2 = connect({
client: clientOrg2,
identity: await newIdentity(certDirectoryPathOrg2, mspIdOrg2),
signer: await newSigner(keyDirectoryPathOrg2),
});
try {
// Get the smart contract from the network for Org1.
const contractOrg1 = gatewayOrg1.getNetwork(channelName).getContract(chaincodeName);
const contractWrapperOrg1 = new ContractWrapper(contractOrg1, mspIdOrg1);
// Get the smart contract from the network for Org2.
const contractOrg2 = gatewayOrg2.getNetwork(channelName).getContract(chaincodeName);
const contractWrapperOrg2 = new ContractWrapper(contractOrg2, mspIdOrg2);
// Create an asset by organization Org1, this only requires the owning organization to endorse.
assetKey = await contractWrapperOrg1.createAsset(mspIdOrg1,
`Asset owned by ${mspIdOrg1} is not for sale`, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Org1 should be able to read the private data details of the asset.
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg1);
// Org2 is not the owner and does not have the private details, read expected to fail.
try {
await contractWrapperOrg2.getAssetPrivateProperties(assetKey, mspIdOrg1);
} catch (e) {
console.log(`${RED}*** Successfully caught the failure: getAssetPrivateProperties - ${String(e)}${RESET}`);
}
// Org1 updates the assets public description.
await contractWrapperOrg1.changePublicDescription({assetId: assetKey,
ownerOrg: mspIdOrg1,
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg1} is for sale`});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// This is an update to the public state and requires the owner(Org1) to endorse and sent by the owner org client (Org1).
// Since the client is from Org2, which is not the owner, this will fail.
try{
await contractWrapperOrg2.changePublicDescription({assetId: assetKey,
ownerOrg: mspIdOrg1,
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg2} is NOT for sale`});
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: changePublicDescription - ${String(e)}${RESET}`);
}
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Agree to a sell by org1.
await contractWrapperOrg1.agreeToSell({
assetId: assetKey,
price: 110,
tradeId: now,
});
// Check the private information about the asset from Org2. Org1 would have to send Org2 asset details,
// so the hash of the details may be checked by the chaincode.
await contractWrapperOrg2.verifyAssetProperties(assetKey, {color:'blue', size:35});
// Agree to a buy by org2.
await contractWrapperOrg2.agreeToBuy( {assetId: assetKey,
price: 100,
tradeId: now}, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
// Org1 should be able to read the sale price of this asset.
await contractWrapperOrg1.getAssetSalesPrice(assetKey, mspIdOrg1);
// Org2 has not set a sale price and this should fail.
try{
await contractWrapperOrg2.getAssetSalesPrice(assetKey, mspIdOrg1);
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: getAssetSalesPrice - ${String(e)}${RESET}`);
}
// Org1 has not agreed to buy so this should fail.
try{
await contractWrapperOrg1.getAssetBidPrice(assetKey, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: getAssetBidPrice - ${String(e)}${RESET}`);
}
// Org2 should be able to see the price it has agreed.
await contractWrapperOrg2.getAssetBidPrice(assetKey, mspIdOrg2);
// Org1 will try to transfer the asset to Org2
// This will fail due to the sell price and the bid price are not the same.
try{
await contractWrapperOrg1.transferAsset({ assetId: assetKey, price: 110, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: transferAsset - ${String(e)}${RESET}`);
}
// Agree to a sell by Org1, the seller will agree to the bid price of Org2.
await contractWrapperOrg1.agreeToSell({assetId:assetKey, price:100, tradeId:now});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Org1 should be able to read the private data details of the asset.
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg1);
// Org1 should be able to read the sale price of this asset.
await contractWrapperOrg1.getAssetSalesPrice(assetKey, mspIdOrg1);
// Org2 should be able to see the price it has agreed.
await contractWrapperOrg2.getAssetBidPrice(assetKey, mspIdOrg2);
// Org2 user will try to transfer the asset to Org1.
// This will fail as the owner is Org1.
try{
await contractWrapperOrg2.transferAsset({ assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: transferAsset - ${String(e)}${RESET}`);
}
// Org1 will transfer the asset to Org2.
// This will now complete as the sell price and the bid price are the same.
await contractWrapperOrg1.transferAsset({ assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg2);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg2);
// Org2 should be able to read the private data details of this asset.
await contractWrapperOrg2.getAssetPrivateProperties(assetKey, mspIdOrg2);
// Org1 should not be able to read the private data details of this asset, expected to fail.
try{
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Successfully caught the failure: getAssetPrivateProperties - ${String(e)}${RESET}`);
}
// This is an update to the public state and requires only the owner to endorse.
// Org2 wants to indicate that the items is no longer for sale.
await contractWrapperOrg2.changePublicDescription( {assetId: assetKey, ownerOrg: mspIdOrg2, publicDescription: `Asset ${assetKey} owned by ${mspIdOrg2} is NOT for sale`});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg2);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg2);
} finally {
gatewayOrg1.close();
gatewayOrg2.close();
clientOrg1.close();
clientOrg2.close();
}
}
main().catch((error: unknown) => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});

View file

@ -1,111 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
// MSP Id's of Organizations
export const mspIdOrg1 = 'Org1MSP';
export const mspIdOrg2 = 'Org2MSP';
// Path to org1 crypto materials.
export const cryptoPathOrg1 = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com');
// Path to user private key directory.
export const keyDirectoryPathOrg1 = path.resolve(cryptoPathOrg1, 'users', 'User1@org1.example.com', 'msp', 'keystore');
// Path to user certificate.
export const certDirectoryPathOrg1 = path.resolve(cryptoPathOrg1, 'users', 'User1@org1.example.com', 'msp', 'signcerts');
// Path to peer tls certificate.
export const tlsCertPathOrg1 = path.resolve(cryptoPathOrg1, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt');
// Path to org2 crypto materials.
export const cryptoPathOrg2 = path.resolve(
__dirname,
'..',
'..',
'..',
'test-network',
'organizations',
'peerOrganizations',
'org2.example.com'
);
// Path to org2 user private key directory.
export const keyDirectoryPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'keystore'
);
// Path to org2 user certificate.
export const certDirectoryPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'signcerts'
);
// Path to org2 peer tls certificate.
export const tlsCertPathOrg2 = path.resolve(
cryptoPathOrg2,
'peers',
'peer0.org2.example.com',
'tls',
'ca.crt'
);
// Gateway peer endpoint.
export const peerEndpointOrg1 = 'localhost:7051';
export const peerEndpointOrg2 = 'localhost:9051';
// Gateway peer container name.
export const peerNameOrg1 = 'peer0.org1.example.com';
export const peerNameOrg2 = 'peer0.org2.example.com';
// Collection Names
export const org1PrivateCollectionName = 'Org1MSPPrivateCollection';
export const org2PrivateCollectionName = 'Org2MSPPrivateCollection';
export async function newGrpcConnection(
tlsCertPath: string,
peerEndpoint: string,
peerName: string
): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': peerName,
});
}
export async function newIdentity(certDirectoryPath: string, mspId: string): Promise<Identity> {
const certPath = await getFirstDirFileName(certDirectoryPath);
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(keyDirectoryPath: string): Promise<Signer> {
const keyPath = await getFirstDirFileName(keyDirectoryPath);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}
async function getFirstDirFileName(dirPath: string): Promise<string> {
const files = await fs.readdir(dirPath);
const file = files[0];
if (!file) {
throw new Error(`No files in directory: ${dirPath}`);
}
return path.join(dirPath, file);
}

View file

@ -1,271 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Contract } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import { GREEN, parse, RED, RESET } from './utils';
import crypto from 'crypto';
import { mspIdOrg2 } from './connect';
const randomBytes = crypto.randomBytes(256).toString('hex');
interface AssetJSON {
objectType: string;
assetID: string;
ownerOrg: string;
publicDescription: string;
}
interface AssetPropertiesJSON {
objectType: string;
color: string;
size: number;
salt: string;
}
interface AssetPriceJSON {
assetID: string;
price: number;
tradeID: string;
}
export interface AssetPrivateData {
ObjectType: string;
Color: string;
Size: number;
}
export interface Asset {
assetId: string;
ownerOrg: string;
publicDescription: string;
}
export interface AssetProperties {
color: string;
size: number;
}
export interface AssetPrice {
assetId: string;
price: number;
tradeId: string;
}
export class ContractWrapper {
readonly #contract: Contract;
readonly #org: string;
readonly #utf8Decoder = new TextDecoder();
readonly #randomBytes: string = randomBytes;
#endorsingOrgs: { [id: string]: string[] };
public constructor(contract: Contract, org: string) {
this.#contract = contract;
this.#org = org;
this.#endorsingOrgs = {};
}
public async createAsset(ownerOrg: string, publicDescription: string, privateData: AssetPrivateData): Promise<string> {
console.log(`${GREEN}--> Submit Transaction: CreateAsset as ${ownerOrg} - endorsed by Org1.${RESET}`);
const assetPropertiesJSON: AssetPropertiesJSON = {
objectType: 'asset_properties',
color: privateData.Color,
size: privateData.Size,
salt: this.#randomBytes };
const resultBytes = await this.#contract.submit('CreateAsset', {
arguments: [publicDescription],
transientData: { asset_properties: JSON.stringify(assetPropertiesJSON)},
});
const assetID = this.#utf8Decoder.decode(resultBytes);
this.#endorsingOrgs[assetID] = [ownerOrg];
console.log(`*** Result: committed, asset ${assetID} is owned by ${ownerOrg}`);
return assetID;
}
public async readAsset(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transactions: ReadAsset as ${this.#org}, - ${assetKey} should be owned by ${ownerOrg}.${RESET}`);
const resultBytes = await this.#contract.evaluateTransaction('ReadAsset', assetKey);
const result = this.#utf8Decoder.decode(resultBytes);
if (result.length !== 0) {
const json = parse<AssetJSON>(result);
if (json.ownerOrg === ownerOrg) {
console.log(`*** Result from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC: ${json.publicDescription}`);
} else {
console.log(`${RED}*** Failed owner check from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC:${json.publicDescription}.${RESET}`);
}
} else {
throw new Error('No Asset Found');
}
}
public async getAssetPrivateProperties(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetPrivateProperties, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== ownerOrg) {
console.log(`${GREEN}* Expected to fail as ${this.#org} is not the owner and does not have the private details.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetPrivateProperties', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPropertiesJSON>(resultString);
const result: AssetProperties = {
color: json.color,
size: json.size,
};
console.log('*** Result:', result);
}
public async changePublicDescription(asset: Asset): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${asset.assetId}, as ${this.#org} - endorse by ${this.#org}.${RESET}`);
if (asset.ownerOrg !== this.#org) {
console.log(`${GREEN}* Expected to fail as ${this.#org} is not the owner.${RESET}`);
}
await this.#contract.submit('ChangePublicDescription', {
arguments:[asset.assetId, asset.publicDescription],
endorsingOrganizations: this.#endorsingOrgs[asset.assetId]
});
console.log(`*** Result: committed, Desc: ${asset.publicDescription}`);
}
public async agreeToSell(assetPrice: AssetPrice): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
const assetPriceJSON: AssetPriceJSON = {
assetID:assetPrice.assetId,
price:assetPrice.price,
tradeID:assetPrice.tradeId
};
await this.#contract.submit('AgreeToSell', {
arguments:[assetPrice.assetId],
transientData: {asset_price: JSON.stringify(assetPriceJSON)},
endorsingOrganizations: this.#endorsingOrgs[assetPrice.assetId]
});
console.log(`*** Result: committed, ${this.#org} has agreed to sell asset ${assetPrice.assetId} for ${String(assetPrice.price)}`);
}
public async verifyAssetProperties(assetId: string, assetProperties: AssetProperties): Promise<void> {
console.log(`${GREEN}--> Evalute: VerifyAssetProperties, ${assetId} as ${this.#org} - endorsed by ${this.#org} and ${mspIdOrg2}.${RESET}`);
const assetPropertiesJSON: AssetPropertiesJSON = {objectType: 'asset_properties',
color: assetProperties.color,
size: assetProperties.size,
salt: this.#randomBytes };
const resultBytes = await this.#contract.evaluate('VerifyAssetProperties', {
arguments:[assetId],
transientData: {asset_properties: JSON.stringify(assetPropertiesJSON)},
});
const resultString = this.#utf8Decoder.decode(resultBytes);
if (resultString.length !== 0) {
const json = parse<AssetPropertiesJSON>(resultString);
if (typeof json === 'object') {
console.log(`*** Success VerifyAssetProperties, private information about asset ${assetId} has been verified by ${this.#org}`);
} else {
console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetId} has not been verified by ${this.#org}`);
}
} else {
throw new Error(`Private information about asset ${assetId} has not been verified by ${this.#org}`);
}
}
public async agreeToBuy(assetPrice: AssetPrice, privateData: AssetPrivateData): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org} and ${mspIdOrg2}.${RESET}`);
const assetPropertiesJSON: AssetPropertiesJSON = {
objectType: 'asset_properties',
color: privateData.Color,
size: privateData.Size,
salt: this.#randomBytes };
const assetPriceJSON: AssetPriceJSON = {
assetID: assetPrice.assetId,
price: assetPrice.price,
tradeID: assetPrice.tradeId
};
await this.#contract.submit('AgreeToBuy', {
arguments:[assetPrice.assetId],
transientData: {
asset_price: JSON.stringify(assetPriceJSON),
asset_properties: JSON.stringify(assetPropertiesJSON)
},
endorsingOrganizations: this.#endorsingOrgs[assetPrice.assetId]
});
console.log(`*** Result: committed, ${this.#org} has agreed to buy asset ${assetPrice.assetId} for 100`);
}
public async getAssetSalesPrice(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetSalesPrice, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== ownerOrg) {
console.log(`${GREEN}* Expected to fail as ${this.#org} has not set a sale price.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetSalesPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString);
const result: AssetPrice = {
assetId: json.assetID,
price: json.price,
tradeId: json.tradeID
};
console.log('*** Result: GetAssetSalesPrice', result);
}
public async getAssetBidPrice(assetKey: string, buyerOrgID: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetBidPrice, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== buyerOrgID){
console.log(`${GREEN}* Expected to fail as ${this.#org} has not agreed to buy.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetBidPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString);
const result: AssetPrice = {
assetId: json.assetID,
price: json.price,
tradeId: json.tradeID,
};
console.log('*** Result: GetAssetBidPrice', result);
}
public async transferAsset(assetPrice: AssetPrice, endorsingOrganizations: string[], ownerOrgID: string, buyerOrgID: string): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetPrice.assetId} as ${this.#org } - endorsed by ${this.#org} and ${buyerOrgID}.${RESET}`);
if (this.#org !== ownerOrgID) {
console.log(`${GREEN}* Expected to fail as the owner is ${ownerOrgID}.${RESET}`);
} else if (assetPrice.price === 110) {
console.log(`${GREEN}* Expected to fail as sell price and the bid price are not the same.${RESET}`);
}
const assetPriceJSON: AssetPriceJSON = { assetID: assetPrice.assetId, price:assetPrice.price, tradeID:assetPrice.tradeId};
await this.#contract.submit('TransferAsset', {
arguments:[assetPrice.assetId, buyerOrgID],
transientData: { asset_price: JSON.stringify(assetPriceJSON) },
endorsingOrganizations: endorsingOrganizations
});
console.log(`${GREEN}*** Result: committed, ${this.#org} has transfered the asset ${assetPrice.assetId} to ${buyerOrgID}.${RESET}`);
}
}

View file

@ -1,13 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
export const RED = '\x1b[31m\n';
export const GREEN = '\x1b[32m\n';
export const RESET = '\x1b[0m';
export function parse<T>(data: string): T {
return JSON.parse(data) as T;
}

View file

@ -1,15 +0,0 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true
},
"include": ["./src/**/*"],
"exclude": ["./src/**/*.spec.ts"]
}

View file

@ -1 +0,0 @@
[Secured asset transfer in Fabric Tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html)

View file

@ -1,619 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"time"
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
const (
typeAssetForSale = "S"
typeAssetBid = "B"
typeAssetSaleReceipt = "SR"
typeAssetBuyReceipt = "BR"
)
type SmartContract struct {
contractapi.Contract
}
// Asset struct and properties must be exported (start with capitals) to work with contract api metadata
type Asset struct {
ObjectType string `json:"objectType"` // ObjectType is used to distinguish different object types in the same chaincode namespace
ID string `json:"assetID"`
OwnerOrg string `json:"ownerOrg"`
PublicDescription string `json:"publicDescription"`
}
type receipt struct {
price int
timestamp time.Time
}
// CreateAsset creates an asset, sets it as owned by the client's org and returns its id
// the id of the asset corresponds to the hash of the properties of the asset that are passed by transiet field
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, publicDescription string) (string, error) {
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return "", fmt.Errorf("error getting transient: %v", err)
}
// Asset properties must be retrieved from the transient field as they are private
immutablePropertiesJSON, ok := transientMap["asset_properties"]
if !ok {
return "", fmt.Errorf("asset_properties key not found in the transient map")
}
// AssetID will be the hash of the asset's properties
hash := sha256.New()
hash.Write(immutablePropertiesJSON)
assetID := hex.EncodeToString(hash.Sum(nil))
// Get the clientOrgId from the input, will be used for implicit collection, owner, and state-based endorsement policy
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return "", err
}
// In this scenario, client is only authorized to read/write private data from its own peer, therefore verify client org id matches peer org id.
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return "", err
}
asset := Asset{
ObjectType: "asset",
ID: assetID,
OwnerOrg: clientOrgID,
PublicDescription: publicDescription,
}
assetBytes, err := json.Marshal(asset)
if err != nil {
return "", fmt.Errorf("failed to create asset JSON: %v", err)
}
err = ctx.GetStub().PutState(assetID, assetBytes)
if err != nil {
return "", fmt.Errorf("failed to put asset in public data: %v", err)
}
// Set the endorsement policy such that an owner org peer is required to endorse future updates.
// In practice, consider additional endorsers such as a trusted third party to further secure transfers.
endorsingOrgs := []string{clientOrgID}
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
if err != nil {
return "", fmt.Errorf("failed setting state based endorsement for buyer and seller: %v", err)
}
// Persist private immutable asset properties to owner's private data collection
collection := buildCollectionName(clientOrgID)
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
if err != nil {
return "", fmt.Errorf("failed to put Asset private details: %v", err)
}
return assetID, nil
}
// ChangePublicDescription updates the assets public description. Only the current owner can update the public description
func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionContextInterface, assetID string, newDescription string) error {
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return err
}
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
// Auth check to ensure that client's org actually owns the asset
if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot update the description of a asset owned by %s", clientOrgID, asset.OwnerOrg)
}
asset.PublicDescription = newDescription
updatedAssetJSON, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset: %v", err)
}
return ctx.GetStub().PutState(assetID, updatedAssetJSON)
}
// AgreeToSell adds seller's asking price to seller's implicit private data collection.
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string) error {
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return err
}
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return err
}
// Verify that this client belongs to the peer's org
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return err
}
// Verify that this clientOrgId actually owns the asset.
if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot sell an asset owned by %s", clientOrgID, asset.OwnerOrg)
}
return agreeToPrice(ctx, assetID, typeAssetForSale)
}
// AgreeToBuy adds buyer's bid price and asset properties to buyer's implicit private data collection
func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, assetID string) error {
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return err
}
// Verify that this client belongs to the peer's org
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return err
}
// Asset properties must be retrieved from the transient field as they are private
immutablePropertiesJSON, ok := transientMap["asset_properties"]
if !ok {
return fmt.Errorf("asset_properties key not found in the transient map")
}
// Persist private immutable asset properties to seller's private data collection
collection := buildCollectionName(clientOrgID)
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
if err != nil {
return fmt.Errorf("failed to put Asset private details: %v", err)
}
return agreeToPrice(ctx, assetID, typeAssetBid)
}
// agreeToPrice adds a bid or ask price to caller's implicit private data collection
func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) error {
// In this scenario, both buyer and seller are authoried to read/write private about transfer after seller agrees to sell.
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return err
}
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
// Asset price must be retrieved from the transient field as they are private
price, ok := transMap["asset_price"]
if !ok {
return fmt.Errorf("asset_price key not found in the transient map")
}
collection := buildCollectionName(clientOrgID)
// Persist the agreed to price in a collection sub-namespace based on priceType key prefix,
// to avoid collisions between private asset properties, sell price, and buy price
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// The Price hash will be verified later, therefore always pass and persist price bytes as is,
// so that there is no risk of nondeterministic marshaling.
err = ctx.GetStub().PutPrivateData(collection, assetPriceKey, price)
if err != nil {
return fmt.Errorf("failed to put asset bid: %v", err)
}
return nil
}
// VerifyAssetProperties allows a buyer to validate the properties of
// an asset they intend to buy against the owner's implicit private data collection
// and verifies that the asset properties never changed from the origin of the asset by checking their hash against the assetID
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return false, fmt.Errorf("error getting transient: %v", err)
}
// Asset properties must be retrieved from the transient field as they are private
immutablePropertiesJSON, ok := transMap["asset_properties"]
if !ok {
return false, fmt.Errorf("asset_properties key not found in the transient map")
}
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return false, fmt.Errorf("failed to get asset: %v", err)
}
collectionOwner := buildCollectionName(asset.OwnerOrg)
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
if err != nil {
return false, fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
}
if immutablePropertiesOnChainHash == nil {
return false, fmt.Errorf("asset private properties hash does not exist: %s", assetID)
}
hash := sha256.New()
hash.Write(immutablePropertiesJSON)
calculatedPropertiesHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
return false, fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x",
calculatedPropertiesHash,
immutablePropertiesJSON,
immutablePropertiesOnChainHash,
)
}
// verify that the hash of the passed immutable properties and on chain hash matches the assetID
if !(hex.EncodeToString(immutablePropertiesOnChainHash) == assetID) {
return false, fmt.Errorf("hash %x for passed immutable properties %s does match on-chain hash %x but do not match assetID %s: asset was altered from its initial form",
calculatedPropertiesHash,
immutablePropertiesJSON,
immutablePropertiesOnChainHash,
assetID)
}
return true, nil
}
// TransferAsset checks transfer conditions and then transfers asset state to buyer.
// TransferAsset can only be called by current owner
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return err
}
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient data: %v", err)
}
priceJSON, ok := transMap["asset_price"]
if !ok {
return fmt.Errorf("asset_price key not found in the transient map")
}
var agreement Agreement
err = json.Unmarshal(priceJSON, &agreement)
if err != nil {
return fmt.Errorf("failed to unmarshal price JSON: %v", err)
}
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
err = verifyTransferConditions(ctx, asset, clientOrgID, buyerOrgID, priceJSON)
if err != nil {
return fmt.Errorf("failed transfer verification: %v", err)
}
err = transferAssetState(ctx, asset, clientOrgID, buyerOrgID, agreement.Price)
if err != nil {
return fmt.Errorf("failed asset transfer: %v", err)
}
return nil
}
// verifyTransferConditions checks that client org currently owns asset and that both parties have agreed on price
func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
asset *Asset,
clientOrgID string,
buyerOrgID string,
priceJSON []byte) error {
// CHECK1: Auth check to ensure that client's org actually owns the asset
if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot transfer a asset owned by %s", clientOrgID, asset.OwnerOrg)
}
// CHECK2: Verify that buyer and seller on-chain asset defintion hash matches
collectionSeller := buildCollectionName(clientOrgID)
collectionBuyer := buildCollectionName(buyerOrgID)
sellerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
if err != nil {
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
}
if sellerPropertiesOnChainHash == nil {
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
}
buyerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, asset.ID)
if err != nil {
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
}
if buyerPropertiesOnChainHash == nil {
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
}
// verify that buyer and seller on-chain asset defintion hash matches
if !bytes.Equal(sellerPropertiesOnChainHash, buyerPropertiesOnChainHash) {
return fmt.Errorf("on chain hash of seller %x does not match on-chain hash of buyer %x",
sellerPropertiesOnChainHash,
buyerPropertiesOnChainHash,
)
}
// CHECK3: Verify that seller and buyer agreed on the same price
// Get sellers asking price
assetForSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
sellerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, assetForSaleKey)
if err != nil {
return fmt.Errorf("failed to get seller price hash: %v", err)
}
if sellerPriceHash == nil {
return fmt.Errorf("seller price for %s does not exist", asset.ID)
}
// Get buyers bid price
assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
buyerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, assetBidKey)
if err != nil {
return fmt.Errorf("failed to get buyer price hash: %v", err)
}
if buyerPriceHash == nil {
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
}
hash := sha256.New()
hash.Write(priceJSON)
calculatedPriceHash := hash.Sum(nil)
// Verify that the hash of the passed price matches the on-chain sellers price hash
if !bytes.Equal(calculatedPriceHash, sellerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, seller hasn't agreed to the passed trade id and price",
calculatedPriceHash,
priceJSON,
sellerPriceHash,
)
}
// Verify that the hash of the passed price matches the on-chain buyer price hash
if !bytes.Equal(calculatedPriceHash, buyerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, buyer hasn't agreed to the passed trade id and price",
calculatedPriceHash,
priceJSON,
buyerPriceHash,
)
}
return nil
}
// transferAssetState performs the public and private state updates for the transferred asset
// changes the endorsement for the transferred asset sbe to the new owner org
func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asset, clientOrgID string, buyerOrgID string, price int) error {
// Update ownership in public state
asset.OwnerOrg = buyerOrgID
updatedAsset, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(asset.ID, updatedAsset)
if err != nil {
return fmt.Errorf("failed to write asset for buyer: %v", err)
}
// Changes the endorsement policy to the new owner org
endorsingOrgs := []string{buyerOrgID}
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new owner: %v", err)
}
// Delete asset description from seller collection
collectionSeller := buildCollectionName(clientOrgID)
err = ctx.GetStub().DelPrivateData(collectionSeller, asset.ID)
if err != nil {
return fmt.Errorf("failed to delete Asset private details from seller: %v", err)
}
// Delete the price records for seller
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for seller: %v", err)
}
err = ctx.GetStub().DelPrivateData(collectionSeller, assetPriceKey)
if err != nil {
return fmt.Errorf("failed to delete asset price from implicit private data collection for seller: %v", err)
}
// Delete the price records for buyer
collectionBuyer := buildCollectionName(buyerOrgID)
assetPriceKey, err = ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for buyer: %v", err)
}
err = ctx.GetStub().DelPrivateData(collectionBuyer, assetPriceKey)
if err != nil {
return fmt.Errorf("failed to delete asset price from implicit private data collection for buyer: %v", err)
}
// Keep record for a 'receipt' in both buyers and sellers private data collection to record the sale price and date.
// Persist the agreed to price in a collection sub-namespace based on receipt key prefix.
receiptBuyKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBuyReceipt, []string{asset.ID, ctx.GetStub().GetTxID()})
if err != nil {
return fmt.Errorf("failed to create composite key for receipt: %v", err)
}
txTimestamp, err := ctx.GetStub().GetTxTimestamp()
if err != nil {
return fmt.Errorf("failed to create timestamp for receipt: %v", err)
}
assetReceipt := receipt{
price: price,
timestamp: txTimestamp.AsTime(),
}
receipt, err := json.Marshal(assetReceipt)
if err != nil {
return fmt.Errorf("failed to marshal receipt: %v", err)
}
err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receipt)
if err != nil {
return fmt.Errorf("failed to put private asset receipt for buyer: %v", err)
}
receiptSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetSaleReceipt, []string{ctx.GetStub().GetTxID(), asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for receipt: %v", err)
}
err = ctx.GetStub().PutPrivateData(collectionSeller, receiptSaleKey, receipt)
if err != nil {
return fmt.Errorf("failed to put private asset receipt for seller: %v", err)
}
return nil
}
// getClientOrgID gets the client org ID.
func getClientOrgID(ctx contractapi.TransactionContextInterface) (string, error) {
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed getting client's orgID: %v", err)
}
return clientOrgID, nil
}
// getClientImplicitCollectionNameAndVerifyClientOrg gets the implicit collection for the client and checks that the client is from the same org as the peer
func getClientImplicitCollectionNameAndVerifyClientOrg(ctx contractapi.TransactionContextInterface) (string, error) {
clientOrgID, err := getClientOrgID(ctx)
if err != nil {
return "", err
}
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return "", err
}
return buildCollectionName(clientOrgID), nil
}
// verifyClientOrgMatchesPeerOrg checks that the client is from the same org as the peer
func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
peerOrgID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting peer's orgID: %v", err)
}
if clientOrgID != peerOrgID {
return fmt.Errorf("client from org %s is not authorized to read or write private data from an org %s peer",
clientOrgID,
peerOrgID,
)
}
return nil
}
// buildCollectionName returns the implicit collection name for an org
func buildCollectionName(clientOrgID string) string {
return fmt.Sprintf("_implicit_org_%s", clientOrgID)
}
// setAssetStateBasedEndorsement adds an endorsement policy to an asset so that the passed orgs need to agree upon transfer
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgsToEndorse []string) error {
endorsementPolicy, err := statebased.NewStateEP(nil)
if err != nil {
return err
}
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgsToEndorse...)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
err = ctx.GetStub().SetStateValidationParameter(assetID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on asset: %v", err)
}
return nil
}
// GetAssetHashId allows a potential buyer to validate the properties of an asset against the asset Id hash on chain and returns the hash
func (s *SmartContract) GetAssetHashId(ctx contractapi.TransactionContextInterface) (string, error) {
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return "", fmt.Errorf("error getting transient: %v", err)
}
// Asset properties must be retrieved from the transient field as they are private
propertiesJSON, ok := transientMap["asset_properties"]
if !ok {
return "", fmt.Errorf("asset_properties key not found in the transient map")
}
hash := sha256.New()
hash.Write(propertiesJSON)
assetID := hex.EncodeToString(hash.Sum(nil))
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return "", fmt.Errorf("failed to get asset: %v, asset properies provided do not represent any on chain asset", err)
}
if asset.ID != assetID {
return "", fmt.Errorf("Asset properies provided do not correpond to any on chain asset")
}
return asset.ID, nil
}
func main() {
chaincode, err := contractapi.NewChaincode(new(SmartContract))
if err != nil {
log.Panicf("Error create transfer asset chaincode: %v", err)
}
if err := chaincode.Start(); err != nil {
log.Panicf("Error starting asset chaincode: %v", err)
}
}

View file

@ -1,172 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// QueryResult structure used for handling result of query
type QueryResult struct {
Record *Asset
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
}
type Agreement struct {
ID string `json:"asset_id"`
Price int `json:"price"`
TradeID string `json:"trade_id"`
}
// ReadAsset returns the public asset data
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
// Since only public data is accessed in this function, no access control is required
assetJSON, err := ctx.GetStub().GetState(assetID)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("%s does not exist", assetID)
}
var asset *Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return asset, nil
}
// GetAssetPrivateProperties returns the immutable asset properties from owner's private data collection
func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(ctx)
if err != nil {
return "", err
}
immutableProperties, err := ctx.GetStub().GetPrivateData(collection, assetID)
if err != nil {
return "", fmt.Errorf("failed to read asset private properties from client org's collection: %v", err)
}
if immutableProperties == nil {
return "", fmt.Errorf("asset private details does not exist in client org's collection: %s", assetID)
}
return string(immutableProperties), nil
}
// GetAssetSalesPrice returns the sales price
func (s *SmartContract) GetAssetSalesPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
return getAssetPrice(ctx, assetID, typeAssetForSale)
}
// GetAssetBidPrice returns the bid price
func (s *SmartContract) GetAssetBidPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
return getAssetPrice(ctx, assetID, typeAssetBid)
}
// getAssetPrice gets the bid or ask price from caller's implicit private data collection
func getAssetPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) (string, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(ctx)
if err != nil {
return "", err
}
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %v", err)
}
price, err := ctx.GetStub().GetPrivateData(collection, assetPriceKey)
if err != nil {
return "", fmt.Errorf("failed to read asset price from implicit private data collection: %v", err)
}
if price == nil {
return "", fmt.Errorf("asset price does not exist: %s", assetID)
}
return string(price), nil
}
// QueryAssetSaleAgreements returns all of an organization's proposed sales
func (s *SmartContract) QueryAssetSaleAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) {
return queryAgreementsByType(ctx, typeAssetForSale)
}
// QueryAssetBuyAgreements returns all of an organization's proposed bids
func (s *SmartContract) QueryAssetBuyAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) {
return queryAgreementsByType(ctx, typeAssetBid)
}
func queryAgreementsByType(ctx contractapi.TransactionContextInterface, agreeType string) ([]Agreement, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(ctx)
if err != nil {
return nil, err
}
// Query for any object type starting with `agreeType`
agreementsIterator, err := ctx.GetStub().GetPrivateDataByPartialCompositeKey(collection, agreeType, []string{})
if err != nil {
return nil, fmt.Errorf("failed to read from private data collection: %v", err)
}
defer agreementsIterator.Close()
var agreements []Agreement
for agreementsIterator.HasNext() {
resp, err := agreementsIterator.Next()
if err != nil {
return nil, err
}
var agreement Agreement
err = json.Unmarshal(resp.Value, &agreement)
if err != nil {
return nil, err
}
agreements = append(agreements, agreement)
}
return agreements, nil
}
// QueryAssetHistory returns the chain of custody for a asset since issuance
func (s *SmartContract) QueryAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]QueryResult, error) {
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var results []QueryResult
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset *Asset
err = json.Unmarshal(response.Value, &asset)
if err != nil {
return nil, err
}
record := QueryResult{
TxId: response.TxId,
Timestamp: response.Timestamp.AsTime(),
Record: asset,
}
results = append(results, record)
}
return results, nil
}

View file

@ -1,34 +0,0 @@
module github.com/hyperledger/fabric-samples/chaincode/tradingMarbles
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0
)
require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1 +0,0 @@
oapi-server.yaml

View file

@ -1,8 +0,0 @@
bin/
keys/
data/
tokenchaincode/zkatdlog_pp.json
auditor/auditor
issuer/issuer
owner/owner

View file

@ -1,19 +0,0 @@
#build stage
FROM golang:1.20.7-bookworm AS builder
WORKDIR /go/src/app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN go build -o /go/bin/app
#final stage
FROM golang:1.20.7-bookworm
COPY --from=builder /go/bin/app /app
ENTRYPOINT /app
LABEL Name=tokens Version=0.1.0
ENV PORT=9000
ENV CONF_DIR=/conf
EXPOSE 9000
EXPOSE 9001

View file

@ -1,413 +0,0 @@
# Token SDK Sample API
This is a service with a REST API that wraps the [Token SDK](https://github.com/hyperledger-labs/fabric-token-sdk) to issue, transfer and redeem tokens backed by a Hyperledger Fabric network for validation and settlement.
Several instances of this service form a Layer 2 network that can transact amongst each other, with an (optional but currently configured to be required) auditor role who has to approve every transaction. The ledger data does not reveal balances, transaction amounts and identities of transaction parties. UTXO Tokens are owned by pseudonymous keys and other details are obscured with Zero Knowledge Proofs.
This sample is intended to get familiar with the features of the Token SDK and as a starting point for a proof of concept. The sample contains a basic development setup with:
- An issuer service
- An auditor service
- Two owner services, with wallets for Alice and Bob (on Owner 1), and Carlos and Dan (on Owner 2)
- A Certificate Authority
- Configuration to use a Fabric test network.
From now on we'll call the services for the issuer, auditor and owners 'nodes' (not to be confused with Hyperledger Fabric peer nodes). Each of them runs as a separate application containing a REST API, the Fabric Smart Client and the Token SDK. The nodes talk to each other via a protocol called libp2p to create token transactions, and each of them also has a Hyperledger Fabric user to be able to submit the transaction to the settlement layer. The settlement layer is just any Fabric network that runs the Token Chaincode, which is configured with the identities of the issuer, auditor and CA to be able to validate transactions.
![components](./components.png)
[plantuml source](https://www.plantuml.com/plantuml/uml/ZPB1IiD048RlUOgXteHM4nIXbD0O4RnO3mKllKmtssR9PYRiRYWYlhlPNPMsIkrjcFs_d-LZvjQXSNsh4ziewj1W2wsYKgErhwfoDQGtrqdIeMXmAs6qv4OIF4ktOzEC02quRk0z0H3STaoI78nMT123iWX9WJ2RmGRNHecnm6awkPtSGPuVmqLVASScCDXN7ffSOLmESRWekauhWKun7RDFrlOoeihQY2g_-vTSx6W8fIkwX6B8I3_SypfKSHgRU4Vd5cMUBz5ejdvwG8fDsQccZptJZy4JBALr1xvhlVdj-qNwluVtRXXJs3Fj5rEDpXVb-PzazaDcPuCBKqdpfPhZlCV6pGayNaXPeoB1bOod94Iqu_oZ-7uBcfPIrCIQjs_UaZ-wyJZtCfAvfAflzIS0)
# Table of Contents
- [Token SDK Sample API](#token-sdk-sample-api)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Getting started](#getting-started)
- [Install dependencies](#install-dependencies)
- [Quick start](#quick-start)
- [Using the application](#using-the-application)
- [Deep dive: what happens when doing a transfer?](#deep-dive-what-happens-when-doing-a-transfer)
- [Alternative: manual start](#alternative-manual-start)
- [Generate crypto material](#generate-crypto-material)
- [Start Fabric and install the chaincode](#start-fabric-and-install-the-chaincode)
- [Start the Token network](#start-the-token-network)
- [View the blockchain explorer](#view-the-blockchain-explorer)
- [Development](#development)
- [End to end tests](#end-to-end-tests)
- [Code structure](#code-structure)
- [Add or change a REST API endpoint](#add-or-change-a-rest-api-endpoint)
- [Upgrade the Token SDK and Fabric Smart Client versions](#upgrade-the-token-sdk-and-fabric-smart-client-versions)
- [Use another Fabric network](#use-another-fabric-network)
- [Add a user / account](#add-a-user--account)
- [Run the service directly (instead of with docker-compose)](#run-the-service-directly-instead-of-with-docker-compose)
## Features
Main flows:
- [X] issue token
- [X] transfer
- [X] redeem / burn
- [X] owner get balances
- [X] owner transaction history
- [ ] auditor get balances
- [X] auditor transaction history
- [ ] issuer transaction history
- [ ] swap
Additional features:
- [X] Documented REST API
- [X] Basic end to end tests
- [X] Support for multiple token types
- [X] Multiple accounts per node
- [X] Use Idemix (privacy preserving) accounts created by a Fabric CA
- [X] Pre-configured and easy to start for development
Out of scope for now:
- HTLC locks (hashed timelock contracts)
- Register/enroll new token accounts on a running network
- Business flows for redemption or issuance
- Advanced transaction history (queries, rolling balance, pagination, etc)
- Denylist / revocation and other business logic for auditor
- Idemix users to submit the transactions to Fabric anonymously
- Production configuration (e.g. deployment, networking, security, resilience, key management)
## Getting started
Prerequisites:
- bash
- golang 1.20+
- git
- docker
- docker-compose
### Install dependencies
Download the Fabric docker images and binaries. The code only works with Fabric CA 1.5.7+, so even if you cloned the fabric-samples repo before, you may have to re-run it to get the latest versions.
From the fabric-samples directory:
```bash
curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh && chmod +x install-fabric.sh
./install-fabric.sh docker binary
```
Make sure that the new binaries are in your path. Change the following line (replace `<your/path/to/>` with the actual path) and add it to your `~/.bashrc` or `~/.zshrc` file. Restart your terminal or `source` the edited file.
```bash
export PATH=</your/path/to/>fabric-samples/bin:$PATH
```
Validate that the CA is at 1.5.7 by executing `fabric-ca-client version`.
> Note: you can run this code from anywhere. If you are *not* running it from the fabric-samples/token-sdk folder, also set the following environment variable:
> ```bash
> export TEST_NETWORK_HOME=</your/path/to>/fabric-samples/test-network
> ```
>
> See the bottom of this readme for instructions to use another Fabric network than the test network.
Install tokengen. Tokengen is a tool to create the configuration file for the token chaincode (once, when deploying the chaincode). It generates the public parameters that the network participants will use to generate their proofs, and it specifies the public identities of the issuer, auditor and CA for signature validation.
```bash
go install github.com/hyperledger-labs/fabric-token-sdk/cmd/tokengen@v0.3.0
```
### Quick start
The quickest way to get going is to run:
```bash
./scripts/up.sh
```
This generates the crypto material, starts Fabric, deploys the chaincode, and starts the token nodes.
When you're done and want to delete everything:
```bash
./scripts/down.sh
```
#### Using the application
The services are accessible on the following ports:
| port | service |
|------|--------------------------|
| 8080 | API documentation (web) |
| 9000 | auditor |
| 9100 | issuer |
| 9200 | owner 1 (alice and bob) |
| 9300 | owner 2 (carlos and dan) |
Besides that, the nodes communicate with each other via 9001, 9101, 9201 and 9301 respectively.
Now let's issue and transfer some tokens! View the API documentation and try some actions at [http://localhost:8080](http://localhost:8080). Or, directly from the commandline:
```bash
curl -X POST http://localhost:9100/api/v1/issuer/issue -H 'Content-Type: application/json' -d '{
"amount": {"code": "TOK","value": 1000},
"counterparty": {"node": "owner1","account": "alice"},
"message": "hello world!"
}'
curl -X GET http://localhost:9200/api/v1/owner/accounts
curl -X GET http://localhost:9300/api/v1/owner/accounts
curl -X POST http://localhost:9200/api/v1/owner/accounts/alice/transfer -H 'Content-Type: application/json' -d '{
"amount": {"code": "TOK","value": 100},
"counterparty": {"node": "owner2","account": "dan"},
"message": "hello dan!"
}'
curl -X GET http://localhost:9300/api/v1/owner/accounts/dan/transactions
curl -X GET http://localhost:9200/api/v1/owner/accounts/alice/transactions
```
Notice that the transaction overview uses the UTXO model (like bitcoin). The issuer created a new TOK token of 1000 and assigned its ownership to alice. When alice transfered 100 TOK to dan, she used the token of 1000 as **input** for her transaction. As **output**, she creates two new tokens:
1. one for 100 TOK with dan as the owner
2. one with _herself_ as the owner for the remaining 900 TOK.
This way, each transaction can have multiple inputs and multiple outputs. Their sum should always be the same, and every new transfer must be based on previously created outputs.
#### Deep dive: what happens when doing a transfer?
It may look simple from the outside, but there's a lot going on to securely and privately transfer tokens. Let's take the example of alice (on the Owner 1 node) transfering 100 TOK to dan (on the Owner 2 node).
1. **Create Transaction**: Alice requests an anonymous key from dan that will own the tokens. She then creates the transaction, with commitments that can be verified by anyone, but _only_ be opened (read) by dan and the auditor. The commitments contain the value, sender and recipient of each of the in- and output tokens.
2. **Get Endorsements**: Alice (or more precisely the TransferView in the Owner 1 node) now submits the transaction to the auditor, who validates and stores it. The auditor _may_ enforce any specific business logic that is needed for this token in this ecosystem (for instance a transaction or holding limit).
Alice then submits the transaction (which is now also signed by the auditor) to the Token Chaincode which is running on the Fabric peers. The chaincode verifies that all the proofs are valid and all the necessary signatures are there. Note that the peer and token chaincode cannot see what is transferred between who thanks to the zero knowledge proofs.
3. **Commit Transaction**: Alice submits the endorsed Fabric transaction to the ordering service. Alice (Owner 1), dan (Owner 2) and the Auditor nodes have been listening for Fabric events involving this transaction. When receiving the 'commit' event, they change the status of the stored transaction to 'Confirmed'. The transaction is now final; dan owns the 100 TOK.
The names of the Views below correspond to the code in `owner/service/transfer.go`, `owner/service/accept.go` and `auditor/service/audit.go`.
![transfer](transfer.png)
[plantuml source](http://www.plantuml.com/plantuml/uml/TLD1JoCz3BtdLrZb0X98yEaxhTGLRA5xu50EQ0-hNZA92r6dpcpY3Eg_tsHcYDmoUvb9hFUUxHVxFh8Ed0wjOiSjmclG57SOWCj16tQUbCf_7s3nq3g32z0HjEeopHdNQM9OR3ueK-wsjALFWLyEFmOezt2n-l6qNZ_ESVuhd0TZiEELZk-LrRXvraEoZdqOMELO2JhPob18xFW8YxLkWZFmWXZYWEhA2IuU_ry_hfxEOPjWCNmYVRb8i5ekOHLGCqflOBbK6cw-bpQ_0N-wTtTx2w-Rvosn1wi9Cd3gLnLUhnc5CJKcs-Q-o3OkomRyap1o_cSR71A3isFjIiefgQCQL_bcB5kJf-F1fxYbIqzum-w0DodY5UpnAF5lI1WA8sAcCkoAuR-VNy3umy7n0OcZ2iWf47IfQPqf2jSJN5ayAOhxwa_45Ws3eouniDyZHTb0XTQQHv0qFDS-qA_19nx-NV1-5tDszqQQKy0hwLryrm6bmBzCyXsIRF1wIpq6jpkEoldBFk2MNf2iepSfENanuD12mDXvYWZdJkG9-eaCMS27Y4EMF3zJjJfPyTJvvbXwKyydarxEbTlhrjcC6AFLyzEYncpZ8jHyiYQHzNHRnflWEkhzVdeYywuT6M-nZ7ojPCwaAPE5ox6oAnZNJs9dZ5iDBoD1rRgwgwNRr6JOdEISbr-tl0PEPST9a7fvFAOHRLflze8e76g2rzReo43uCG4jVicUhPsOvqimD3r4SaZdoERvr9kpcr2IqrsL1Bfn4gsJbSCqHoWGTOzaqw7z2m00)
### Alternative: manual start
To get a better view or have more control on the different layers of the network, you can also start the services manually. If you want to do that, first bring down everything with `./scripts/down.sh`.
#### Generate crypto material
In this step, we create all the identities which are used by the Token network. We use a normal Fabric CA for this. Technically, only the Owner identities (the wallets that will hold the tokens) need some form of hierarchy; they use Idemix credentials which must be issued by a single, known issuer (see [Fabric documentation](https://hyperledger-fabric.readthedocs.io/en/latest/idemix.html) for more info about idemix). To keep things simple, we use the same CA for the other identities too. The Token SDK expects the folders for the identities to be in Fabric's 'msp' structure.
The following crypto will be generated:
- Fabric Smart Client node identities, used by the nodes to authenticate each other
- Token Issuer identity (x509 certificate and private key)
- Token Auditor identity (x509 certificate and private key)
- Owner identities (idemix credentials)
```bash
mkdir -p keys/ca
docker-compose -f compose-ca.yaml up -d
./scripts/enroll-users.sh
```
> If you want, you can stop the CA now. You don't need it unless you want to register more users.
>
> ```bash
> docker-compose -f compose-ca.yaml down
> ```
The Issuer and Auditor identities are used by the Token Chaincode to validate token transactions. It also needs the identity of the CA that issues the Idemix credentials to the Owner wallets. The tokengen command generates the configuration that contains these identities and the cryptographic parameters for the proofs. We store it in the `tokenchaincode` folder, so that it will be baked into the chaincode docker image later.
```bash
tokengen gen dlog --base 300 --exponent 5 --issuers keys/issuer/iss/msp --idemix keys/owner1/wallet/alice --auditors keys/auditor/aud/msp --output tokenchaincode
```
> You only have to do this once. But if for any reason you want to re-generate the material: `rm -rf keys; rm tokenchaincode/zkatdlog_pp.json` and execute the steps above again. If any owner has existing tokens, they will now be invalid because the old proofs can not be verified with the new parameters.
#### Start Fabric and install the chaincode
For simplicity, in this sample all nodes use the credentials of User1 from Org1MSP and have Peer1 as a trusted peer. In a more serious setup, each instance would have its own (idemix) Fabric user and _may_ have it's own MSP and peers, depending on the network topology and trust relationships.
Start a Fabric sample network and deploy the Token Chaincode as a service:
```bash
../test-network/network.sh up createChannel
INIT_REQUIRED="--init-required" ../test-network/network.sh deployCCAAS -ccn tokenchaincode -ccp $(pwd)/tokenchaincode -cci "init" -verbose -ccs 1
mkdir -p keys/fabric && cp -r ../test-network/organizations keys/fabric/
```
> To fully remove the whole network:
> ```bash
> docker stop peer0org1_tokenchaincode_ccaas peer0org2_tokenchaincode_ccaas
> ../test-network/network.sh" down
> rm -rf keys/fabric
> ```
#### Start the Token network
> On the bottom of this document you'll find instructions to run the nodes as golang binaries natively instead of with docker compose.
```bash
rm -rf data/auditor data/issuer data/owner1 data/owner2
mkdir -p data/auditor data/issuer data/owner1 data/owner2
docker-compose up -d
```
Visit [http://localhost:8080](http://localhost:8080) to view the API documentation and execute some transactions.
### View the blockchain explorer
As a bonus, this sample contains configuration to connect the [blockchain explorer](https://github.com/hyperledger-labs/blockchain-explorer/) with the fabric-samples network. It allows you to inspect the transactions which are committed to the ledger. It shows more of what the Token SDK does under the covers.
Start it as follows:
```bash
cd explorer
docker-compose up -d
```
And visit it in the browser on [localhost:8081](http://localhost:8081).
To tear it down, do this (the -v is important; it removes the volumes that contain the identities and blocks):
```bash
docker-compose down -v
```
## Development
### End to end tests
See the `e2e` folder for some tests that exercise the APIs. The end to end tests require the services to be running. They create new transactions, so don't run them on a deployment you want to keep clean.
```bash
go test ./e2e -count=1 -v
```
### Code structure
This repo contains 3 different, isolated golang applications, one for each of the roles: *issuer*, *auditor*, and *owner*. They are maintained separately and each have their own dependencies. In a production scenario these would have their own lifecycle, and most likely be maintained and deployed by different organizations.
The code structure of each of the roles is the same. There is overlap between the roles; each has the boilerplate code to start the Fabric Smart Client and Token SDK. The main.go is almost identical; the only difference is which 'responders' the application registers. Also the contents of the routes and the services will depend on the features that a role needs:
- Issuers can issue funds
- Auditors see and sign every transaction
- Owners can transfer funds.
Here's an example of the code structure for the auditor:
```
auditor
├── main.go
├── oapi-server.yaml
├── conf
│ └── core.yaml
├── routes
│ ├── operations.go
│ ├── routes.gen.go
│ ├── routes.go
│ └── server.go
└── service
├── audit.go
├── balance.go
└── history.go
```
As you can see, the business logic is all in the 'service' directory. The 'routes' are purely the code needed for the REST API. We chose to use *openapi-codegen* to generate the code for the routes, and *echo* as the server. The 'routes' package is just the presentation layer; you could easily replace it and call the code from the 'service' package from somewhere else. For instance if you wanted to create a CLI application for the issuer!
![dependencies](./dependencies.png)
[plantuml](http://www.plantuml.com/plantuml/uml/RP71QlCm3CVlVWhHxnpw1X_jeR32e6FhRVIWEafgubX6ThgMqNTVQZjqAOD0PFd7pt_Pgn1Huj1RrGZs18lTboE19Mn365An7ceJMHRmhG36ht1R5qaSMl2eEsmfB00369ymWCyUZJlSM_S2_gszjqPZDEpoll0GAIGYbtymWUHiD2KedFKpSLEGxLLT_Ry3itMsAfZq1NbCy7Br99RgbWHUyHZcav2FqoWD7iNeAlGeiTBMa8ifKXF6I7lI9yUMs-iCZjoHgqBT9NByFv7pxEIZWZHYMJnIxkA91EYIRxj4uocQxzebYR24GvPcINQo-kNPN9xU2neMUDzyx67zjYrUdBoCtbIQQsh97NAhCwvYJsxSAPrn7fwEBPTSJaPrKojozT3R7m00)
For more information about how we interact with the Token SDK, check out an example on the [Token SDK GitHub](https://github.com/hyperledger-labs/fabric-token-sdk/blob/main/samples/fungible/README.md).
### Add or change a REST API endpoint
We generate the API based on `swagger.yaml`. To keep things a bit simple, we have only one definition which includes all of the roles (even though they are separate applications, running on different ports!) Any changes should be made in this file first. Then generate the code with:
```bash
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
oapi-codegen -config auditor/oapi-server.yaml swagger.yaml
oapi-codegen -config issuer/oapi-server.yaml swagger.yaml
oapi-codegen -config owner/oapi-server.yaml swagger.yaml
oapi-codegen -config e2e/oapi-client.yaml swagger.yaml
```
### Upgrade the Token SDK and Fabric Smart Client versions
Token SDK and Fabric Smart Client are under active development. To upgrade to the latest versions:
- change the commit hash of fabric-smart-client and fabric-token-sdk in {auditor,issuer,owner}/go.mod (and do `go mod tidy`)
- change the commit hash in tokenchaincode/Dockerfile
- install tokengen with the new commit hash
- update the readme
### Use another Fabric network
Of course you're not tied to the Fabric samples testnetwork. If you want to anchor your Token services to another blockchain, you have to:
1. Deploy the token chaincode with the generated parameters to the Fabric network
2. Configure the token services with the correct channel, peer and orderer addresses and certs, MSP configuration, and a user (see the `core.yaml` files in the `conf` dir).
### Add a user / account
To add another user, simply register and enroll it at the Token CA (see `scripts/enroll-users.sh`), and configure it at one of the owner nodes (see `conf` dir).
### Run the service directly (instead of with docker-compose)
For a faster development cycle, you may choose to run the services outside of docker. It requires some adjustments to your environment to make the paths and routes work.
Add the following to your `/etc/hosts`:
```
127.0.0.1 peer0.org1.example.com
127.0.0.1 peer0.org2.example.com
127.0.0.1 orderer.example.com
127.0.0.1 owner1.example.com
127.0.0.1 owner2.example.com
127.0.0.1 auditor.example.com
127.0.0.1 issuer.example.com
```
> The Token SDK discovers the peer addresses from the channel config (after connecting to a configured trusted peer).
For the paths you have two options:
1. Find/replace all instances of /var/fsc in the conf directory with the path to this repo. **Or**
2. Create a symlink to this folder to make the configuration files work (they don't play nice with relative paths):
```bash
sudo ln -s "${PWD}" /var/fsc
```
The advantage of this approach is that the configuration is portable across developer laptops and works with docker-compose as well as without.
Start the blockchain and deploy the chaincode (see above).
Instead of doing docker-compose up, start the token services with (each in their own terminal):
```bash
mkdir bin
go build -o bin/auditor ./auditor
go build -o bin/issuer ./issuer
go build -o bin/owner ./owner
PORT=9000 CONF_DIR=./auditor/conf ./bin/auditor
PORT=9100 CONF_DIR=./issuer/conf ./bin/issuer
PORT=9200 CONF_DIR=./owner/conf/owner1 ./bin/owner
PORT=9300 CONF_DIR=./owner/conf/owner2 ./bin/owner
```
Now you can use the REST APIs to control the services (see the swagger definition).
When you made changes in the code, stop a service with CTRL+C, `go build -o bin/owner ./owner` and start it again.
If you want to reset the transaction history:
```bash
rm -rf data/auditor data/issuer data/owner1 data/owner2 && mkdir data/auditor data/issuer data/owner1 data/owner2
```

View file

@ -1,112 +0,0 @@
logging:
spec: info
format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}'
# ------------------- FSC Node Configuration -------------------------
# The FSC node is responsible for the peer to peer communication with other token services.
fsc:
identity:
cert:
file: /var/fsc/keys/auditor/fsc/msp/signcerts/cert.pem
key:
file: /var/fsc/keys/auditor/fsc/msp/keystore/priv_sk
tls:
enabled: false # TODO
p2p:
listenAddress: /ip4/0.0.0.0/tcp/9001
# If empty, this is a P2P boostrap node. Otherwise, it contains the name of the FSC node that is a bootstrap node.
# The name of the FSC node that is a bootstrap node must be set under fsc.endpoint.resolvers
bootstrapNode:
kvs: # key-value-store
persistence:
type: badger # badger or memory
opts:
path: /var/fsc/data/auditor/kvs
# The endpoint section tells how to reach other FSC node in the network.
# For each node, the name, the domain, the identity of the node, and its addresses must be specified.
endpoint:
resolvers:
- name: issuer
identity:
id: issuer
path: /var/fsc/keys/issuer/fsc/msp/signcerts/cert.pem
addresses:
P2P: issuer.example.com:9101
- name: owner1
identity:
id: owner1
path: /var/fsc/keys/owner1/fsc/msp/signcerts/cert.pem
addresses:
P2P: owner1.example.com:9201
- name: owner2
identity:
id: owner2
path: /var/fsc/keys/owner2/fsc/msp/signcerts/cert.pem
addresses:
P2P: owner2.example.com:9301
# ------------------- Fabric Configuration -------------------------
fabric:
enabled: true
mynetwork:
default: true
mspConfigPath: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
defaultMSP: Org1MSP
msps:
- id: Org1MSP
mspType: bccsp
mspID: Org1MSP
path: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
tls:
enabled: true
# If the keepalive values are too low, Fabric peers will complain with: ENHANCE_YOUR_CALM, debug data: "too_many_pings"
keepalive:
interval: 300s
timeout: 600s
# List of orderer nodes this node can connect to. There must be at least one orderer node. Others are discovered.
orderers:
- address: orderer.example.com:7050
connectionTimeout: 10s
tlsEnabled: true
tlsRootCertFile: /var/fsc/keys/fabric/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
serverNameOverride: orderer.example.com
# List of trusted peers this node can connect to. There must be at least one trusted peer. Others are discovered.
peers:
- address: peer0.org1.example.com:7051
connectionTimeout: 10s
tlsEnabled: true
tlsRootCertFile: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
serverNameOverride: peer0.org1.example.com
# Channel where the token chaincode is deployed
channels:
- name: mychannel
default: true
# Configuration of the vault used to store the RW sets assembled by this node
vault:
persistence:
type: badger
opts:
path: /var/fsc/data/auditor/vault
# ------------------- Token SDK Configuration -------------------------
token:
enabled: true
tms:
mytms: # unique name of this token management system
network: mynetwork # the name of the fabric network as configured above
channel: mychannel # the name of the network's channel this TMS refers to, if applicable
namespace: tokenchaincode # chaincode name
driver: zkatdlog # privacy preserving driver (zero knowledge asset transfer)
wallets:
auditors:
- id: auditor # the unique identifier of this wallet. Here is an example of use: `ttx.GetIssuerWallet(context, "issuer)`
default: true # is this the default issuer wallet
path: /var/fsc/keys/auditor/aud/msp
# Internal database to keep track of token transactions.
# It is used by auditors and token owners to track history
ttxdb:
persistence:
type: badger
opts:
path: /var/fsc/data/auditor/txdb

View file

@ -1,245 +0,0 @@
module github.com/hyperledger/fabric-samples/token-sdk/auditor
go 1.20
replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go/codec v1.2.9
require (
github.com/deepmap/oapi-codegen v1.15.0
github.com/getkin/kin-openapi v0.120.0
github.com/hyperledger-labs/fabric-smart-client v0.3.0
github.com/hyperledger-labs/fabric-token-sdk v0.3.0
github.com/labstack/echo/v4 v4.11.1
github.com/pkg/errors v0.9.1
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/IBM/idemix v0.0.2-0.20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/schemes/aries v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/types v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/mathlib v0.0.3-0.20230831091907-c532c4d3b65c // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/ale-linux/aries-framework-go/component/kmscrypto v0.0.0-20230817163708-4b3de6d91874 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.9.1 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.2 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huin/goupnp v1.2.0 // indirect
github.com/hyperledger-labs/orion-sdk-go v0.2.5 // indirect
github.com/hyperledger-labs/orion-server v0.2.5 // indirect
github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go v1.2.3-alpha.1 // indirect
github.com/hyperledger-labs/weaver-dlt-interoperability/sdks/fabric/go-sdk v1.2.3-alpha.1.0.20210812140206-37f430515b8c // indirect
github.com/hyperledger/fabric v1.4.0-rc1.0.20230401164317-bd8e24856939 // indirect
github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect
github.com/hyperledger/fabric-chaincode-go v0.0.0-20220920210243-7bc6fa0dd58b // indirect
github.com/hyperledger/fabric-lib-go v1.0.0 // indirect
github.com/hyperledger/fabric-private-chaincode v0.0.0-20210907122433-d56466264e4d // indirect
github.com/hyperledger/fabric-protos-go v0.2.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/ipfs/boxo v0.8.0-rc1 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.20.0 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kataras/blocks v0.0.7 // indirect
github.com/kataras/golog v0.1.9 // indirect
github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 // indirect
github.com/kataras/pio v0.0.12 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.31.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.22.0 // indirect
github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-nat v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.11.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
github.com/quic-go/webtransport-go v0.5.3 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.10.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/sykesm/zap-logfmt v0.0.4 // indirect
github.com/tdewolff/minify/v2 v2.12.9 // indirect
github.com/tdewolff/parse/v2 v2.6.8 // indirect
github.com/test-go/testify v1.1.4 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/yosssi/ace v0.0.5 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.13.0 // indirect
go.opentelemetry.io/otel/sdk v1.13.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/dig v1.17.0 // indirect
go.uber.org/fx v1.20.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

File diff suppressed because it is too large Load diff

View file

@ -1,114 +0,0 @@
package main
import (
"net/http"
"os"
"os/signal"
"syscall"
"github.com/hyperledger/fabric-samples/token-sdk/auditor/routes"
"github.com/hyperledger/fabric-samples/token-sdk/auditor/service"
"github.com/hyperledger-labs/fabric-smart-client/pkg/api"
"github.com/hyperledger-labs/fabric-smart-client/pkg/node"
fabric "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/sdk"
viewregistry "github.com/hyperledger-labs/fabric-smart-client/platform/view"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging"
tokensdk "github.com/hyperledger-labs/fabric-token-sdk/token/sdk"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
)
var logger = flogging.MustGetLogger("main")
func main() {
dir := getEnv("CONF_DIR", "./conf")
port := getEnv("PORT", "9000")
fsc := startFabricSmartClient(dir)
// Tell the service how to respond to other nodes when they initiate an action
registry := viewregistry.GetRegistry(fsc)
succeedOrPanic(registry.RegisterResponder(&service.AuditView{}, &ttx.AuditingViewInitiator{}))
controller := routes.Controller{Service: service.TokenService{FSC: fsc}}
err := routes.StartWebServer(port, controller, logger)
if err != nil {
if err == http.ErrServerClosed {
logger.Infof("Webserver closing, exiting...", err.Error())
fsc.Stop()
} else {
logger.Fatalf("echo error - %s", err.Error())
fsc.Stop()
os.Exit(1)
}
}
}
type Node interface {
api.ServiceProvider
Stop()
}
func startFabricSmartClient(confDir string) Node {
logger.Infof("Initializing Fabric Smart Client and Token SDK...")
fsc := node.NewFromConfPath(confDir)
succeedOrPanic(fsc.InstallSDK(fabric.NewSDK(fsc)))
succeedOrPanic(fsc.InstallSDK(tokensdk.NewSDK(fsc)))
succeedOrPanic(fsc.Start())
// Stop gracefully
go handleSignals((map[os.Signal]func(){
syscall.SIGINT: func() {
logger.Info("Stopping FSC node...")
fsc.Stop()
os.Exit(130)
},
syscall.SIGTERM: func() {
logger.Info("Stopping FSC node...")
fsc.Stop()
os.Exit(143)
},
syscall.SIGSTOP: func() {
logger.Info("Stopping FSC node...")
fsc.Stop()
os.Exit(145)
},
syscall.SIGHUP: func() {
logger.Info("Stopping FSC node...")
fsc.Stop()
os.Exit(129)
},
}))
logger.Infof("FSC node is ready!")
return fsc
}
// getEnv returns an environment variable or the fallback
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func succeedOrPanic(err error) {
if err != nil {
logger.Fatalf("Failed initializing Token SDK - %s", err.Error())
os.Exit(1)
}
}
func handleSignals(handlers map[os.Signal]func()) {
var signals []os.Signal
for sig := range handlers {
signals = append(signals, sig)
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, signals...)
for sig := range signalChan {
logger.Infof("Received signal: %d (%s)", sig, sig)
handlers[sig]()
}
}

View file

@ -1,11 +0,0 @@
package: routes
generate:
echo-server: true
strict-server: true
models: true
embedded-spec: true
output-options:
include-tags:
- operations
- auditor
output: auditor/routes/routes.gen.go

View file

@ -1,31 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package routes
import (
"context"
)
// (GET /readyz)
func (c Controller) Readyz(ctx context.Context, request ReadyzRequestObject) (ReadyzResponseObject, error) {
// TODO: what defines readiness if the REST API is available after FSC?
return Readyz200JSONResponse{
HealthSuccessJSONResponse: HealthSuccessJSONResponse{
Message: "ok",
},
}, nil
}
// (GET /healthz)
func (c Controller) Healthz(ctx context.Context, request HealthzRequestObject) (HealthzResponseObject, error) {
// TODO: how to determine health?
return Healthz200JSONResponse{
HealthSuccessJSONResponse: HealthSuccessJSONResponse{
Message: "ok",
},
}, nil
}

View file

@ -1,579 +0,0 @@
// Package auditor provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.13.4 DO NOT EDIT.
package routes
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/deepmap/oapi-codegen/pkg/runtime"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
)
// Account Information about an account and its balance
type Account struct {
// Balance balance in base units for each currency
Balance []Amount `json:"balance"`
// Id account id as registered at the Certificate Authority
Id string `json:"id"`
}
// Amount The amount to issue, transfer or redeem.
type Amount struct {
// Code the code of the token
Code string `json:"code"`
// Value value in base units (usually cents)
Value int64 `json:"value"`
}
// Error defines model for Error.
type Error struct {
// Message High level error message
Message string `json:"message"`
// Payload Details about the error
Payload string `json:"payload"`
}
// TransactionRecord A transaction
type TransactionRecord struct {
// Amount The amount to issue, transfer or redeem.
Amount Amount `json:"amount"`
// Id transaction id
Id string `json:"id"`
// Message user provided message
Message string `json:"message"`
// Recipient the recipient of the transaction
Recipient string `json:"recipient"`
// Sender the sender of the transaction
Sender string `json:"sender"`
// Status Unknown | Pending | Confirmed | Deleted
Status string `json:"status"`
// Timestamp timestamp in the format: "2018-03-20T09:12:28Z"
Timestamp time.Time `json:"timestamp"`
}
// Code The token code to filter on
type Code = string
// Id account id as registered at the Certificate Authority
type Id = string
// AccountSuccess defines model for AccountSuccess.
type AccountSuccess struct {
Message string `json:"message"`
// Payload Information about an account and its balance
Payload Account `json:"payload"`
}
// ErrorResponse defines model for ErrorResponse.
type ErrorResponse = Error
// HealthSuccess defines model for HealthSuccess.
type HealthSuccess struct {
// Message ok
Message string `json:"message"`
}
// TransactionsSuccess defines model for TransactionsSuccess.
type TransactionsSuccess struct {
Message string `json:"message"`
Payload []TransactionRecord `json:"payload"`
}
// AuditorAccountParams defines parameters for AuditorAccount.
type AuditorAccountParams struct {
Code *Code `form:"code,omitempty" json:"code,omitempty"`
}
// ServerInterface represents all server handlers.
type ServerInterface interface {
// Get an account and their balance of a certain type
// (GET /auditor/accounts/{id})
AuditorAccount(ctx echo.Context, id Id, params AuditorAccountParams) error
// Get all transactions for an account
// (GET /auditor/accounts/{id}/transactions)
AuditorTransactions(ctx echo.Context, id Id) error
// (GET /healthz)
Healthz(ctx echo.Context) error
// (GET /readyz)
Readyz(ctx echo.Context) error
}
// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
// AuditorAccount converts echo context to params.
func (w *ServerInterfaceWrapper) AuditorAccount(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id Id
err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
// Parameter object where we will unmarshal all parameters from the context
var params AuditorAccountParams
// ------------- Optional query parameter "code" -------------
err = runtime.BindQueryParameter("form", true, false, "code", ctx.QueryParams(), &params.Code)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter code: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.AuditorAccount(ctx, id, params)
return err
}
// AuditorTransactions converts echo context to params.
func (w *ServerInterfaceWrapper) AuditorTransactions(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id Id
err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.AuditorTransactions(ctx, id)
return err
}
// Healthz converts echo context to params.
func (w *ServerInterfaceWrapper) Healthz(ctx echo.Context) error {
var err error
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.Healthz(ctx)
return err
}
// Readyz converts echo context to params.
func (w *ServerInterfaceWrapper) Readyz(ctx echo.Context) error {
var err error
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.Readyz(ctx)
return err
}
// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
type EchoRouter interface {
CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}
// RegisterHandlers adds each server route to the EchoRouter.
func RegisterHandlers(router EchoRouter, si ServerInterface) {
RegisterHandlersWithBaseURL(router, si, "")
}
// Registers handlers, and prepends BaseURL to the paths, so that the paths
// can be served under a prefix.
func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
wrapper := ServerInterfaceWrapper{
Handler: si,
}
router.GET(baseURL+"/auditor/accounts/:id", wrapper.AuditorAccount)
router.GET(baseURL+"/auditor/accounts/:id/transactions", wrapper.AuditorTransactions)
router.GET(baseURL+"/healthz", wrapper.Healthz)
router.GET(baseURL+"/readyz", wrapper.Readyz)
}
type AccountSuccessJSONResponse struct {
Message string `json:"message"`
// Payload Information about an account and its balance
Payload Account `json:"payload"`
}
type ErrorResponseJSONResponse Error
type HealthSuccessJSONResponse struct {
// Message ok
Message string `json:"message"`
}
type TransactionsSuccessJSONResponse struct {
Message string `json:"message"`
Payload []TransactionRecord `json:"payload"`
}
type AuditorAccountRequestObject struct {
Id Id `json:"id"`
Params AuditorAccountParams
}
type AuditorAccountResponseObject interface {
VisitAuditorAccountResponse(w http.ResponseWriter) error
}
type AuditorAccount200JSONResponse struct{ AccountSuccessJSONResponse }
func (response AuditorAccount200JSONResponse) VisitAuditorAccountResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type AuditorAccountdefaultJSONResponse struct {
Body Error
StatusCode int
}
func (response AuditorAccountdefaultJSONResponse) VisitAuditorAccountResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(response.StatusCode)
return json.NewEncoder(w).Encode(response.Body)
}
type AuditorTransactionsRequestObject struct {
Id Id `json:"id"`
}
type AuditorTransactionsResponseObject interface {
VisitAuditorTransactionsResponse(w http.ResponseWriter) error
}
type AuditorTransactions200JSONResponse struct {
TransactionsSuccessJSONResponse
}
func (response AuditorTransactions200JSONResponse) VisitAuditorTransactionsResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type AuditorTransactionsdefaultJSONResponse struct {
Body Error
StatusCode int
}
func (response AuditorTransactionsdefaultJSONResponse) VisitAuditorTransactionsResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(response.StatusCode)
return json.NewEncoder(w).Encode(response.Body)
}
type HealthzRequestObject struct {
}
type HealthzResponseObject interface {
VisitHealthzResponse(w http.ResponseWriter) error
}
type Healthz200JSONResponse struct{ HealthSuccessJSONResponse }
func (response Healthz200JSONResponse) VisitHealthzResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type Healthz503JSONResponse struct{ ErrorResponseJSONResponse }
func (response Healthz503JSONResponse) VisitHealthzResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(503)
return json.NewEncoder(w).Encode(response)
}
type ReadyzRequestObject struct {
}
type ReadyzResponseObject interface {
VisitReadyzResponse(w http.ResponseWriter) error
}
type Readyz200JSONResponse struct{ HealthSuccessJSONResponse }
func (response Readyz200JSONResponse) VisitReadyzResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type Readyz503JSONResponse struct{ ErrorResponseJSONResponse }
func (response Readyz503JSONResponse) VisitReadyzResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(503)
return json.NewEncoder(w).Encode(response)
}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
// Get an account and their balance of a certain type
// (GET /auditor/accounts/{id})
AuditorAccount(ctx context.Context, request AuditorAccountRequestObject) (AuditorAccountResponseObject, error)
// Get all transactions for an account
// (GET /auditor/accounts/{id}/transactions)
AuditorTransactions(ctx context.Context, request AuditorTransactionsRequestObject) (AuditorTransactionsResponseObject, error)
// (GET /healthz)
Healthz(ctx context.Context, request HealthzRequestObject) (HealthzResponseObject, error)
// (GET /readyz)
Readyz(ctx context.Context, request ReadyzRequestObject) (ReadyzResponseObject, error)
}
type StrictHandlerFunc = runtime.StrictEchoHandlerFunc
type StrictMiddlewareFunc = runtime.StrictEchoMiddlewareFunc
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
}
// AuditorAccount operation middleware
func (sh *strictHandler) AuditorAccount(ctx echo.Context, id Id, params AuditorAccountParams) error {
var request AuditorAccountRequestObject
request.Id = id
request.Params = params
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
return sh.ssi.AuditorAccount(ctx.Request().Context(), request.(AuditorAccountRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "AuditorAccount")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.(AuditorAccountResponseObject); ok {
return validResponse.VisitAuditorAccountResponse(ctx.Response())
} else if response != nil {
return fmt.Errorf("Unexpected response type: %T", response)
}
return nil
}
// AuditorTransactions operation middleware
func (sh *strictHandler) AuditorTransactions(ctx echo.Context, id Id) error {
var request AuditorTransactionsRequestObject
request.Id = id
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
return sh.ssi.AuditorTransactions(ctx.Request().Context(), request.(AuditorTransactionsRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "AuditorTransactions")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.(AuditorTransactionsResponseObject); ok {
return validResponse.VisitAuditorTransactionsResponse(ctx.Response())
} else if response != nil {
return fmt.Errorf("Unexpected response type: %T", response)
}
return nil
}
// Healthz operation middleware
func (sh *strictHandler) Healthz(ctx echo.Context) error {
var request HealthzRequestObject
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
return sh.ssi.Healthz(ctx.Request().Context(), request.(HealthzRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "Healthz")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.(HealthzResponseObject); ok {
return validResponse.VisitHealthzResponse(ctx.Response())
} else if response != nil {
return fmt.Errorf("Unexpected response type: %T", response)
}
return nil
}
// Readyz operation middleware
func (sh *strictHandler) Readyz(ctx echo.Context) error {
var request ReadyzRequestObject
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
return sh.ssi.Readyz(ctx.Request().Context(), request.(ReadyzRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "Readyz")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.(ReadyzResponseObject); ok {
return validResponse.VisitReadyzResponse(ctx.Response())
} else if response != nil {
return fmt.Errorf("Unexpected response type: %T", response)
}
return nil
}
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/9RZUW/bNhD+KwduDy2gRrKzDa3esrZYi70UaQoMbfNwls4WG4pUScqZl/q/DyQlWbJk",
"J826onlKJFLH7767++4I37BMlZWSJK1h6Q2rUGNJlrR/ylRO7i+XLGWfa9IbFjGJJbE0rEXMZAWV6Dbl",
"ZDLNK8uV231REFh1RRLcRrAKllxY0qAkixj9jWUlnJmX787/YhGzm8o9Gau5XLHtNmI8706u0Ba7g3nO",
"Iqbpc8015Sy1uqbDMDDLVC0t8BzQgKYVN5Y05YAWbEHwnLTlS56hJTirbaE0t5sBQBQ8owmEWwfCVEoa",
"8lydhZPe1llGpmFPWpLW/YtVJdwhXMn4k3HIbnqQK60qhyMYKskYXHne986MWIUbodAz87OmJUvZT/Eu",
"gHEwaeIGCwsgW6Y+dKZ3hi47x9TiE2U2ODbksHEJWncdkJdaK33evvgaZ4/h9lanIPiFAYBXhMIW92G7",
"C22PaqauPL2HAjFEo64mM3aK6fvye6FRGszcDvNNU2qX2LZ3BGiymtOa8rFng6zjlkpzWxh74M8pUzp3",
"RhqrqDVu/rfE3LZK0C/JcQBfy6XSpecOcKFqCyihlQqUOXBrYIECpS/9Xsa0L9MPrTq2CrZGURNLZ0mS",
"JNuoW3339kVvdZ4k28ugbY2wjLKuO2EfdLMAXMICDUEtHcql0kCYFZDVWpPMnHjdKUhnZZCI/ci0yvu9",
"dHSYCF7cWwrGORCxBvZkv0G/5noNN6amCHyKL13TceKRE5Unw3AeCuEoKm0nzGmJtbC7b4YoHBW+36ml",
"p8V3wKmSao7a98K/3ovwo9rUKMQGMhe/xyxiIXldK5T2t19YxEoueVmXLE26o7i0tCI9Irhp2+H8KYKD",
"Bh/SSfJCPC7XlH2Ffr7iqwIErUnAvr1j2jM08oIscmGa+nVke1t3VuZjUjPQ30bCRgDOoKegw7TCLkmP",
"JJgrs9n8NOqx6webjFfcazxbqIWbsEjmpHsVZCza2rCUPVdyyXUZRJuXZCyWFUvZPJk9fZKcPpknF8mz",
"dDZP50/fj8OzA3k3mZiShR4DwCd7x8EkqA1pqLRa85zyYxnQY+Rmoty65a7mBlEZmWvpnLIV1u5qqAnD",
"vqF38kqqawlf4A3JnMsVfIEuUvAFXpAgO91oe0EcwWuXnDo4dEEEUvg4Ge6PrK8TOVp64izcTX8bivrU",
"R2269EF2HES3zDtcLtVEFw4iXSiR96Tatd+g1UE9zQn8jtkV5bDYAELOHfJFbSkHQfmKdPRRVpoM6bXj",
"utJ8jdkGauOe3pNW8KdU134rvNFKLc2Jd8L6rnThj3ClSdoEWLOTxMVCVSSx4ixlpyfJyanXC1v4eMdY",
"59wqHTdd0cQ3PN+6lRX5LHVl5qeL104Zz8LudhqJBpesD9Plt9sSczc+3brLC42bLQaXknmSHCrwbl+8",
"d3PxY1bT5W77dHgN8OMX6XXr2N4IEWhgEau1YCkrrK3SOBYqQ1EoY9NnSZLEWPF4PYu9K6YuS9QblrI/",
"aDSi2YK4boc0V7IIGWmLrjxcBkbM4srh6A6+/KbwttGBPIj7g/VtSdGf8++VGfeK+NTt4ocNuxAwuKq4",
"iXeXC98lzoW/bP5zMJivmvX7xGJ4kd1G7Nfk9D4RaFnokJlAREfmOdlaSwPzJAEeOpwXTXelMBBc9PN/",
"7KdnHf74a+VBMsPOI1zODsbWq38j8W5k74XUYVDXknaFdRyFH4y8KISJ6RCYeR9MtG8lQy2U8WZylEfM",
"nI7yYwi26wYPC3Ecuu4PDHyYzX5EeLSotXzcpBE75Nm+Ij+wwLSz0QMJzUU7yvWLW9nCDXe9CteE+eaw",
"pp6H5Qcsqd5B732WUWUhQyHMt+2W0X8T5OgHy6GG8NFNu+n8uEYucCH8zwk7pprfBdoXYzyT33dMtT8r",
"hOc7fu3LFK5RCLJmZ8S/nrDxtsmK0GWbOw7mXLoE3X29y7Pt5fbfAAAA///PKdGnmxkAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file
// or error if failed to decode
func decodeSpec() ([]byte, error) {
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
if err != nil {
return nil, fmt.Errorf("error base64 decoding spec: %w", err)
}
zr, err := gzip.NewReader(bytes.NewReader(zipped))
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
var buf bytes.Buffer
_, err = buf.ReadFrom(zr)
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
return buf.Bytes(), nil
}
var rawSpec = decodeSpecCached()
// a naive cached of a decoded swagger spec
func decodeSpecCached() func() ([]byte, error) {
data, err := decodeSpec()
return func() ([]byte, error) {
return data, err
}
}
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
res := make(map[string]func() ([]byte, error))
if len(pathToFile) > 0 {
res[pathToFile] = rawSpec
}
return res
}
// GetSwagger returns the Swagger specification corresponding to the generated code
// in this file. The external references of Swagger specification are resolved.
// The logic of resolving external references is tightly connected to "import-mapping" feature.
// Externally referenced files must be embedded in the corresponding golang packages.
// Urls can be supported but this task was out of the scope.
func GetSwagger() (swagger *openapi3.T, err error) {
resolvePath := PathToRawSpec("")
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
pathToFile := url.String()
pathToFile = path.Clean(pathToFile)
getSpec, ok := resolvePath[pathToFile]
if !ok {
err1 := fmt.Errorf("path not found: %s", pathToFile)
return nil, err1
}
return getSpec()
}
var specData []byte
specData, err = rawSpec()
if err != nil {
return
}
swagger, err = loader.LoadFromData(specData)
if err != nil {
return
}
return
}

View file

@ -1,100 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package routes
import (
"context"
"fmt"
"github.com/hyperledger/fabric-samples/token-sdk/auditor/service"
)
type Controller struct {
Service service.TokenService
}
// Get an account and their balance of a certain type
// (GET /auditor/accounts/{id})
func (c Controller) AuditorAccount(ctx context.Context, request AuditorAccountRequestObject) (AuditorAccountResponseObject, error) {
if request.Params.Code == nil {
return AuditorAccountdefaultJSONResponse{
Body: Error{
Message: "code is required",
Payload: "",
},
StatusCode: 400,
}, nil
}
balance, err := c.Service.GetBalance(request.Id, *request.Params.Code)
if err != nil {
return AuditorAccountdefaultJSONResponse{
Body: Error{
Message: "can't get account",
Payload: err.Error(),
},
StatusCode: 500,
}, nil
}
amounts := []Amount{}
for typ, val := range balance {
amounts = append(amounts, Amount{
Code: typ,
Value: val,
})
}
return AuditorAccount200JSONResponse{
AccountSuccessJSONResponse: AccountSuccessJSONResponse{
Message: fmt.Sprintf("got %s's %s", request.Id, *request.Params.Code),
Payload: Account{
Id: request.Id,
Balance: amounts,
},
},
}, nil
}
// Get all transactions for an account
// (GET /owner/accounts/{id}/transactions)
func (c Controller) AuditorTransactions(ctx context.Context, request AuditorTransactionsRequestObject) (AuditorTransactionsResponseObject, error) {
var history []service.TransactionHistoryItem
var err error
history, err = c.Service.GetHistory(request.Id)
if err != nil {
return AuditorTransactionsdefaultJSONResponse{
Body: Error{
Message: "can't get history",
Payload: err.Error(),
},
StatusCode: 500,
}, nil
}
pl := []TransactionRecord{}
for _, tx := range history {
pl = append(pl, TransactionRecord{
Amount: Amount{
Code: tx.TokenType,
Value: tx.Amount,
},
Id: tx.TxID,
Recipient: tx.Recipient,
Sender: tx.Sender,
Status: tx.Status,
Timestamp: tx.Timestamp,
Message: tx.Message,
})
}
return AuditorTransactions200JSONResponse{
TransactionsSuccessJSONResponse: TransactionsSuccessJSONResponse{
Message: fmt.Sprintf("got %d transactions for %s", len(pl), request.Id),
Payload: pl,
},
}, nil
}

View file

@ -1,65 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package routes
import (
"fmt"
"log"
"os"
oapimiddleware "github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/labstack/echo/v4"
middleware "github.com/labstack/echo/v4/middleware"
)
type Logger interface {
Infof(template string, args ...interface{})
Debugf(template string, args ...interface{})
Warnf(template string, args ...interface{})
Errorf(template string, args ...interface{})
Fatalf(template string, args ...interface{})
}
// Start web server on the main thread. It exits the application if it fails setting up.
func StartWebServer(port string, routesImplementation StrictServerInterface, logger Logger) error {
e := echo.New()
baseURL := "/api/v1"
handler := NewStrictHandler(routesImplementation, nil)
RegisterHandlersWithBaseURL(e, handler, baseURL)
// Request validator
swagger, err := GetSwagger()
if err != nil {
log.Fatalf("Error loading swagger spec\n: %s", err)
os.Exit(1)
}
swagger.Servers = nil
e.Group(baseURL).Use(oapimiddleware.OapiRequestValidator(swagger))
e.Use(middleware.CORS())
e.Use(middleware.RequestID())
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
Skipper: func(c echo.Context) bool {
return c.Path() == "/api/v1/healthz" || c.Path() == "/api/v1/readyz"
},
LogRequestID: true, LogMethod: true, LogURI: true, LogStatus: true, LogLatency: true,
LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
if v.Status < 400 {
logger.Infof("%d %s %s %s [%s]", v.Status, v.Method, v.URI, v.Latency.String(), v.RequestID)
} else if v.Status >= 400 && v.Status < 500 {
logger.Warnf("%d %s %s %s [%s]", v.Status, v.Method, v.URI, v.Latency.String(), v.RequestID)
} else {
logger.Errorf("%d %s %s %s [%s]", v.Status, v.Method, v.URI, v.Latency.String(), v.RequestID)
}
return nil
},
}))
// Start REST API server
return e.Start(fmt.Sprintf("0.0.0.0:%s", port))
}

View file

@ -1,68 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package service
import (
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
"github.com/pkg/errors"
)
var logger = flogging.MustGetLogger("service")
// VIEW
// Auditing is initiated as a response to an audit request from another
// FSC node (not via an internal service or API).
type AuditView struct{}
func (v *AuditView) Call(context view.Context) (interface{}, error) {
logger.Infof("incoming session from [%s]", context.Session().Info().Endpoint)
tx, err := ttx.ReceiveTransaction(context)
if err != nil {
err = errors.Wrap(err, "failed receiving transaction")
logger.Error(err.Error())
return "", err
}
// get auditor wallet
w := ttx.MyAuditorWallet(context)
if w == nil {
err = errors.New("failed getting default auditor wallet")
logger.Error(err.Error())
return "", err
}
auditor := ttx.NewAuditor(context, w)
// Validate
err = auditor.Validate(tx)
if err != nil {
err = errors.Wrapf(err, "transaction invalid: [%s]", tx.ID())
logger.Error(err.Error())
return "", err
}
// See https://github.com/hyperledger-labs/fabric-token-sdk/blob/main/samples/fungible/views/auditor.go for examples of auditor checks
logger.Infof("transaction valid: [%s]", tx.ID())
res, err := context.RunView(ttx.NewAuditApproveView(w, tx))
if err != nil {
logger.Error(err.Error())
return "", err
}
logger.Infof("transaction committed: [%s]", tx.ID())
return res, err
}
type RegisterAuditorView struct{}
func (r *RegisterAuditorView) Call(context view.Context) (interface{}, error) {
return context.RunView(ttx.NewRegisterAuditorView(
&AuditView{},
))
}

View file

@ -1,51 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package service
import (
"github.com/hyperledger-labs/fabric-smart-client/pkg/api"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
"github.com/pkg/errors"
)
type TokenService struct {
FSC api.ServiceProvider
}
// SERVICE
type ValueByTokenType map[string]int64
// GetBalance returns the balances per token type of a wallet
func (s TokenService) GetBalance(wallet string, tokenType string) (typeVal ValueByTokenType, err error) {
typeVal = make(ValueByTokenType)
// get auditor wallet
w := ttx.MyAuditorWallet(s.FSC)
if w == nil {
err = errors.New("failed getting default auditor wallet")
logger.Error(err.Error())
return
}
auditor := ttx.NewAuditor(s.FSC, w)
aqe := auditor.NewQueryExecutor()
defer aqe.Done()
// TODO: how to get all TokenTypes separately?
filter, err := aqe.NewHoldingsFilter().ByEnrollmentId(wallet).ByType(tokenType).Execute()
if err != nil {
err = errors.Wrapf(err, "failed retrieving holding for [%s][%s]", wallet, tokenType)
logger.Error(err.Error())
return
}
currentHolding := filter.Sum()
typeVal[tokenType] = currentHolding.Int64()
logger.Debugf("Current Holding: [%s][%s][%d]", wallet, tokenType, typeVal[tokenType])
return
}

View file

@ -1,105 +0,0 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package service
import (
"time"
"github.com/hyperledger-labs/fabric-token-sdk/token"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttxdb"
"github.com/pkg/errors"
)
// SERVICE
type TransactionHistoryItem struct {
// TxID is the transaction ID
TxID string
// ActionType is the type of action performed by this transaction record
ActionType int
// SenderEID is the enrollment ID of the account that is sending tokens
Sender string
// RecipientEID is the enrollment ID of the account that is receiving tokens
Recipient string
// TokenType is the type of token
TokenType string
// Amount is positive if tokens are received. Negative otherwise
Amount int64
// Timestamp is the time the transaction was submitted to the db
Timestamp time.Time
// Status is the status of the transaction
Status string
// Message is the user message sent with the transaction. It comes from
// the ApplicationMetadata and is sent in the transient field
Message string
}
// GetHistory returns the full transaction history for an auditor.
func (s TokenService) GetHistory(wallet string) (txs []TransactionHistoryItem, err error) {
// get auditor wallet
w := ttx.MyAuditorWallet(s.FSC)
if w == nil {
err = errors.New("failed getting default auditor wallet")
logger.Error(err.Error())
return txs, err
}
auditor := ttx.NewAuditor(s.FSC, w)
// Get query executor
aqe := auditor.NewQueryExecutor()
defer aqe.Done()
// This retrieves all transactions to *or* from the provided wallet.
// See QueryTransactionsParams interface for additional filters.
it, err := aqe.Transactions(ttxdb.QueryTransactionsParams{
SenderWallet: wallet,
RecipientWallet: wallet,
})
if err != nil {
return txs, errors.New("failed querying transactions")
}
defer it.Close()
// we need transaction info to get the transient field (application metadata)
tip := ttx.NewTransactionInfoProvider(s.FSC, token.GetManagementService(s.FSC))
if tip == nil {
return txs, errors.New("failed to get transactionInfoProvider")
}
// Return the list of audited transactions
for {
tx, err := it.Next()
if tx == nil {
break
}
transaction := TransactionHistoryItem{
TxID: tx.TxID,
ActionType: int(tx.ActionType),
Sender: tx.SenderEID,
Recipient: tx.RecipientEID,
TokenType: tx.TokenType,
Amount: tx.Amount.Int64(),
Timestamp: tx.Timestamp.UTC(),
Status: string(tx.Status),
}
if err != nil {
return txs, errors.New("failed iterating over transactions")
}
// set user provided message from transient field
ti, err := tip.TransactionInfo(transaction.TxID)
if err != nil {
return txs, err
}
if ti.ApplicationMetadata != nil && string(ti.ApplicationMetadata["message"]) != "" {
transaction.Message = string(ti.ApplicationMetadata["message"])
}
txs = append(txs, transaction)
}
return
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,23 +0,0 @@
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
version: '3.7'
services:
ca_token_network:
image: hyperledger/fabric-ca:1.5.7
labels:
service: hyperledger-fabric
environment:
- FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
- FABRIC_CA_SERVER_CA_NAME=ca-token-network
- FABRIC_CA_SERVER_TLS_ENABLED=false
- FABRIC_CA_SERVER_PORT=27054
ports:
- "27054:27054"
command: sh -c 'fabric-ca-server start -b admin:adminpw --idemix.curve gurvy.Bn254 -d'
volumes:
- ${PWD}/keys/ca:/etc/hyperledger/fabric-ca-server
container_name: ca_token_network

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View file

@ -1,101 +0,0 @@
version: '3.7'
# fabric_test is the name of the fabric-samples test network.
# By connecting to it, we can reach the peers at their DNS names
# (e.g. peer0.org1.example.com).
networks:
test:
name: fabric_test
external: true
services:
auditor:
hostname: auditor.example.com
restart: always
build:
context: ./auditor
dockerfile: ../Dockerfile
volumes:
- ./data/auditor:/var/fsc/data/auditor
- ./auditor/conf:/conf:ro
- ./keys:/var/fsc/keys:ro
ports:
- 9000:9000
expose:
- 9001
networks:
- test
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/api/v1/readyz"]
interval: "5s"
timeout: "1s"
retries: 20
issuer:
hostname: issuer.example.com
restart: always
build:
context: ./issuer
dockerfile: ../Dockerfile
volumes:
- ./data/issuer:/var/fsc/data/issuer
- ./issuer/conf:/conf:ro
- ./keys:/var/fsc/keys:ro
ports:
- 9100:9000
expose:
- 9101
networks:
- test
depends_on:
auditor:
condition: service_healthy
owner1:
hostname: owner1.example.com
restart: always
build:
context: ./owner
dockerfile: ../Dockerfile
volumes:
- ./data/owner1:/var/fsc/data/owner1
- ./owner/conf/owner1:/conf:ro
- ./keys:/var/fsc/keys:ro
ports:
- 9200:9000
expose:
- 9201
networks:
- test
depends_on:
auditor:
condition: service_healthy
owner2:
hostname: owner2.example.com
restart: always
build:
context: ./owner
dockerfile: ../Dockerfile
volumes:
- ./data/owner2:/var/fsc/data/owner2
- ./owner/conf/owner2:/conf:ro
- ./keys:/var/fsc/keys:ro
ports:
- 9300:9000
expose:
- 9301
networks:
- test
depends_on:
auditor:
condition: service_healthy
swagger-ui:
image: swaggerapi/swagger-ui
ports:
- '8080:8080'
environment:
- URL=/swagger.yaml
volumes:
- ./swagger.yaml:/usr/share/nginx/html/swagger.yaml

File diff suppressed because it is too large Load diff

View file

@ -1,249 +0,0 @@
package e2e
import (
"context"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var auditor *ClientWithResponses
var issuer *ClientWithResponses
var err error
var CODE string = "TEST"
var alice = Counterparty{
Account: "alice",
Node: "owner1",
}
var bob = Counterparty{
Account: "bob",
Node: "owner1",
}
var dan = Counterparty{
Account: "dan",
Node: "owner2",
}
type ownerAPI struct {
client *ClientWithResponses
}
var owner1 ownerAPI
var owner2 ownerAPI
func TestMain(t *testing.T) {
auditor, err = NewClientWithResponses(getEnv("AUDITOR_URL", "http://localhost:9000/api/v1"))
assert.NoError(t, err, "failed creating client")
issuer, err = NewClientWithResponses(getEnv("ISSUER_URL", "http://localhost:9100/api/v1"))
assert.NoError(t, err, "failed creating client")
client1, err := NewClientWithResponses(getEnv("OWNER1_URL", "http://localhost:9200/api/v1"))
assert.NoError(t, err, "failed creating client")
owner1 = ownerAPI{client: client1}
client2, err := NewClientWithResponses(getEnv("OWNER2_URL", "http://localhost:9300/api/v1"))
assert.NoError(t, err, "failed creating client")
owner2 = ownerAPI{client: client2}
// we have to issue funds to alice first to be able to do the other tests
testIssuance(t)
}
func testIssuance(t *testing.T) {
accBefore := owner1.getAccounts(t)
txBefore := owner1.getTransactions(t, "alice")
id := issue(t, alice, 1000)
acc2 := owner1.getAccounts(t)
txAfter := owner1.getTransactions(t, "alice")
assert.Equal(t, len(txBefore)+1, len(txAfter), "should have 1 issue transaction more", txAfter)
assert.Equal(t, getValue(t, accBefore, "alice")+1000, getValue(t, acc2, "alice"), acc2)
lastTx := txAfter[len(txAfter)-1]
assert.Equal(t, id, lastTx.Id)
assert.Equal(t, int64(1000), lastTx.Amount.Value)
assert.Equal(t, "alice", lastTx.Recipient)
}
func TestTransfer(t *testing.T) {
accBefore := owner1.getAccounts(t)
txBefore := owner1.getTransactions(t, "alice")
id := owner1.transfer(t, "alice", bob, 100)
accAfter := owner1.getAccounts(t)
txAfter := owner1.getTransactions(t, "alice")
assert.Equal(t, getValue(t, accBefore, "alice")-100, getValue(t, accAfter, "alice"), accAfter)
assert.Equal(t, getValue(t, accBefore, "bob")+100, getValue(t, accAfter, "bob"), accAfter)
assert.Greater(t, len(txAfter), len(txBefore))
// on the sender side there may be several transactions, so we check the recipient
txBob := owner1.getTransactions(t, "bob")
lastTx := txBob[len(txBob)-1]
assert.Equal(t, id, lastTx.Id, txBob)
assert.Equal(t, lastTx.Amount.Value, int64(100))
}
func TestTransferToSecondNode(t *testing.T) {
// current state
acc1Before := owner1.getAccounts(t)
tx1Before := owner1.getTransactions(t, "alice")
acc2Before := owner2.getAccounts(t)
tx2Before := owner2.getTransactions(t, "dan")
// transfer 100 from alice to dan
id := owner1.transfer(t, "alice", dan, 100)
// after: alice
acc1After := owner1.getAccounts(t)
assert.Equal(t, getValue(t, acc1Before, "alice")-100, getValue(t, acc1After, "alice"), acc1After) // -100 TEST
tx1After := owner1.getTransactions(t, "alice")
assert.Greater(t, len(tx1After), len(tx1Before)) // +1 tx
// after: dan
acc2After := owner2.getAccounts(t)
assert.Equal(t, getValue(t, acc2Before, "dan")+100, getValue(t, acc2After, "dan"), acc2After) // +100 TEST
tx2After := owner2.getTransactions(t, "dan")
assert.Greater(t, len(tx2After), len(tx2Before)) // + 1 tx
// on the sender side there may be several transactions, so we check the recipient
txDan := owner2.getTransactions(t, "dan")
lastTx := txDan[len(txDan)-1]
assert.Equal(t, id, lastTx.Id, txDan)
assert.Equal(t, lastTx.Amount.Value, int64(100))
owner2.testIfAuditorMatchesOwnerHistory(t, []string{"dan"})
}
func TestRedeem(t *testing.T) {
accBefore := owner1.getAccounts(t)
id := owner1.redeem(t, "alice", 10, "test redeem")
accAfter := owner1.getAccounts(t)
transactions := owner1.getTransactions(t, "alice")
assert.Equal(t, getValue(t, accBefore, "alice")-10, getValue(t, accAfter, "alice"), accAfter)
lastTx := transactions[len(transactions)-1]
assert.Equal(t, id, lastTx.Id, transactions)
assert.Equal(t, lastTx.Amount.Value, int64(10))
assert.Equal(t, "alice", lastTx.Sender)
assert.Equal(t, "test redeem", lastTx.Message)
}
func TestIfAuditorMatchesOwnerHistory(t *testing.T) {
owner1.testIfAuditorMatchesOwnerHistory(t, []string{"alice", "bob"})
owner2.testIfAuditorMatchesOwnerHistory(t, []string{"carlos", "dan"})
}
func issue(t *testing.T, counterparty Counterparty, value int64) string {
res, err := issuer.IssueWithResponse(context.TODO(), IssueJSONRequestBody{
Amount: Amount{
Code: CODE,
Value: value,
},
Counterparty: alice,
Message: new(string),
})
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func getAuditorTransactions(t *testing.T, wallet string) []TransactionRecord {
res, err := auditor.AuditorTransactionsWithResponse(context.TODO(), wallet)
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func (o *ownerAPI) testIfAuditorMatchesOwnerHistory(t *testing.T, accounts []string) {
for _, w := range accounts {
tx := o.getTransactions(t, w)
audittx := getAuditorTransactions(t, w)
assert.Equal(t, len(tx), len(audittx), w)
// Timestamp is the time of storing the tx in the database
// so it's not the same on both sides.
for i := 0; i < len(tx); i++ {
tx[i].Timestamp = time.Time{}
audittx[i].Timestamp = time.Time{}
}
assert.Equal(t, tx, audittx)
}
}
func (o *ownerAPI) getTransactions(t *testing.T, wallet string) []TransactionRecord {
res, err := o.client.OwnerTransactionsWithResponse(context.TODO(), wallet)
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func (o *ownerAPI) transfer(t *testing.T, sender string, counterparty Counterparty, value int64) string {
res, err := o.client.TransferWithResponse(context.TODO(), sender, TransferJSONRequestBody{
Amount: Amount{
Code: CODE,
Value: value,
},
Counterparty: counterparty,
Message: new(string),
})
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func (o *ownerAPI) redeem(t *testing.T, wallet string, value int64, message string) string {
res, err := o.client.RedeemWithResponse(context.TODO(), wallet, RedeemJSONRequestBody{
Amount: Amount{
Code: CODE,
Value: value,
},
Message: &message,
})
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func (o *ownerAPI) getAccounts(t *testing.T) []Account {
res, err := o.client.OwnerAccountsWithResponse(context.TODO())
assert.NoError(t, err)
assert.Nil(t, res.JSONDefault)
assert.NotNil(t, res.JSON200)
t.Logf(res.JSON200.Message)
return res.JSON200.Payload
}
func getValue(t *testing.T, acc []Account, wallet string) int64 {
for _, a := range acc {
if a.Id == wallet {
for _, b := range a.Balance {
if b.Code == CODE {
return b.Value
}
}
}
}
t.Logf("%s value not found for wallet %s in %v", CODE, wallet, acc)
return 0
}
// getEnv returns an environment variable or the fallback
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}

View file

@ -1,3 +0,0 @@
module github.com/hyperledger/fabric-samples/token-sdk/e2e
go 1.20

View file

@ -1,5 +0,0 @@
package: e2e
generate:
client: true
models: true
output: e2e/client.gen.go

View file

@ -1,4 +0,0 @@
PORT=8081
EXPLORER_CONFIG_FILE_PATH=./config.json
EXPLORER_PROFILE_DIR_PATH=./connection-profile
FABRIC_CRYPTO_PATH=../../test-network/organizations

View file

@ -1,9 +0,0 @@
{
"network-configs": {
"test-network": {
"name": "Test Network",
"profile": "./connection-profile/test-network.json"
}
},
"license": "Apache-2.0"
}

View file

@ -1,48 +0,0 @@
{
"name": "test-network",
"version": "1.0.0",
"client": {
"tlsEnable": true,
"adminCredential": {
"id": "exploreradmin",
"password": "exploreradminpw"
},
"enableAuthentication": true,
"organization": "Org1MSP",
"connection": {
"timeout": {
"peer": {
"endorser": "300"
},
"orderer": "300"
}
}
},
"channels": {
"mychannel": {
"peers": {
"peer0.org1.example.com": {}
}
}
},
"organizations": {
"Org1MSP": {
"mspid": "Org1MSP",
"adminPrivateKey": {
"path": "/tmp/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk"
},
"peers": ["peer0.org1.example.com"],
"signedCert": {
"path": "/tmp/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem"
}
}
},
"peers": {
"peer0.org1.example.com": {
"tlsCACerts": {
"path": "/tmp/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
},
"url": "grpcs://peer0.org1.example.com:7051"
}
}
}

View file

@ -1,63 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# see https://github.com/hyperledger-labs/blockchain-explorer
version: '2.1'
volumes:
pgdata:
walletstore:
networks:
test:
name: fabric_test
external: true
services:
explorerdb.mynetwork.com:
image: ghcr.io/hyperledger-labs/explorer-db:latest
container_name: explorerdb.mynetwork.com
hostname: explorerdb.mynetwork.com
environment:
- DATABASE_DATABASE=fabricexplorer
- DATABASE_USERNAME=hppoc
- DATABASE_PASSWORD=password
healthcheck:
test: "pg_isready -h localhost -p 5432 -q -U postgres"
interval: 30s
timeout: 10s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- test
explorer.mynetwork.com:
image: ghcr.io/hyperledger-labs/explorer:latest
container_name: explorer.mynetwork.com
hostname: explorer.mynetwork.com
environment:
- DATABASE_HOST=explorerdb.mynetwork.com
- DATABASE_DATABASE=fabricexplorer
- DATABASE_USERNAME=hppoc
- DATABASE_PASSWD=password
- LOG_LEVEL_APP=info
- LOG_LEVEL_DB=info
- LOG_LEVEL_CONSOLE=debug
- LOG_CONSOLE_STDOUT=true
- DISCOVERY_AS_LOCALHOST=false
- PORT=${PORT:-8080}
volumes:
- ${EXPLORER_CONFIG_FILE_PATH}:/opt/explorer/app/platform/fabric/config.json
- ${EXPLORER_PROFILE_DIR_PATH}:/opt/explorer/app/platform/fabric/connection-profile
- ${FABRIC_CRYPTO_PATH}:/tmp/crypto
- walletstore:/opt/explorer/wallet
ports:
- ${PORT:-8080}:${PORT:-8080}
depends_on:
explorerdb.mynetwork.com:
condition: service_healthy
networks:
- test

View file

@ -1,6 +0,0 @@
go 1.20
use ./auditor
use ./issuer
use ./owner
use ./e2e

View file

@ -1,7 +0,0 @@
github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBEK16QnXY=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=

View file

@ -1,116 +0,0 @@
logging:
spec: info
format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}'
# ------------------- FSC Node Configuration -------------------------
# The FSC node is responsible for the peer to peer communication with other token services.
fsc:
identity:
cert:
file: /var/fsc/keys/issuer/fsc/msp/signcerts/cert.pem
key:
file: /var/fsc/keys/issuer/fsc/msp/keystore/priv_sk
tls:
enabled: false # TODO
p2p:
listenAddress: /ip4/0.0.0.0/tcp/9101
# If empty, this is a P2P boostrap node. Otherwise, it contains the name of the FSC node that is a bootstrap node.
# The name of the FSC node that is a bootstrap node must be set under fsc.endpoint.resolvers
bootstrapNode: auditor
kvs: # key-value-store
persistence:
type: badger # badger or memory
opts:
path: /var/fsc/data/issuer/kvs
# The endpoint section tells how to reach other FSC node in the network.
# For each node, the name, the domain, the identity of the node, and its addresses must be specified.
endpoint:
resolvers:
- name: auditor
identity:
id: auditor
path: /var/fsc/keys/auditor/fsc/msp/signcerts/cert.pem
addresses:
P2P: auditor.example.com:9001
- name: owner1
identity:
id: owner1
path: /var/fsc/keys/owner1/fsc/msp/signcerts/cert.pem
addresses:
P2P: owner1.example.com:9201
aliases:
- owner1
- name: owner2
identity:
id: owner2
path: /var/fsc/keys/owner2/fsc/msp/signcerts/cert.pem
addresses:
P2P: owner2.example.com:9301
aliases:
- owner2
# ------------------- Fabric Configuration -------------------------
fabric:
enabled: true
mynetwork:
default: true
mspConfigPath: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
defaultMSP: Org1MSP
msps:
- id: Org1MSP
mspType: bccsp
mspID: Org1MSP
path: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
tls:
enabled: true
# If the keepalive values are too low, Fabric peers will complain with: ENHANCE_YOUR_CALM, debug data: "too_many_pings"
keepalive:
interval: 300s
timeout: 600s
# List of orderer nodes this node can connect to. There must be at least one orderer node. Others are discovered.
orderers:
- address: orderer.example.com:7050
connectionTimeout: 10s
tlsEnabled: true
tlsRootCertFile: /var/fsc/keys/fabric/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
serverNameOverride: orderer.example.com
# List of trusted peers this node can connect to. There must be at least one trusted peer. Others are discovered.
peers:
- address: peer0.org1.example.com:7051
connectionTimeout: 10s
tlsEnabled: true
tlsRootCertFile: /var/fsc/keys/fabric/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
serverNameOverride: peer0.org1.example.com
# Channel where the token chaincode is deployed
channels:
- name: mychannel
default: true
# Configuration of the vault used to store the RW sets assembled by this node
vault:
persistence:
type: badger
opts:
path: /var/fsc/data/issuer/vault
# ------------------- Token SDK Configuration -------------------------
token:
enabled: true
tms:
mytms: # unique name of this token management system
network: mynetwork # the name of the fabric network as configured above
channel: mychannel # the name of the network's channel this TMS refers to, if applicable
namespace: tokenchaincode # chaincode name
driver: zkatdlog # privacy preserving driver (zero knowledge asset transfer)
wallets:
issuers:
- id: issuer # the unique identifier of this wallet. Here is an example of use: `ttx.GetIssuerWallet(context, "issuer)`
default: true # is this the default issuer wallet
path: /var/fsc/keys/issuer/iss/msp
# Internal database to keep track of token transactions.
# It is used by auditors and token owners to track history
ttxdb:
persistence:
type: badger
opts:
path: /var/fsc/data/issuer/txdb

View file

@ -1,245 +0,0 @@
module github.com/hyperledger/fabric-samples/token-sdk/issuer
go 1.20
replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go/codec v1.2.9
require (
github.com/deepmap/oapi-codegen v1.15.0
github.com/getkin/kin-openapi v0.120.0
github.com/hyperledger-labs/fabric-smart-client v0.3.0
github.com/hyperledger-labs/fabric-token-sdk v0.3.0
github.com/labstack/echo/v4 v4.11.1
github.com/pkg/errors v0.9.1
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/IBM/idemix v0.0.2-0.20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/schemes/aries v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/idemix/bccsp/types v0.0.0-20230831093709-b7a940638990 // indirect
github.com/IBM/mathlib v0.0.3-0.20230831091907-c532c4d3b65c // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/ale-linux/aries-framework-go/component/kmscrypto v0.0.0-20230817163708-4b3de6d91874 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.9.1 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.2 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huin/goupnp v1.2.0 // indirect
github.com/hyperledger-labs/orion-sdk-go v0.2.5 // indirect
github.com/hyperledger-labs/orion-server v0.2.5 // indirect
github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go v1.2.3-alpha.1 // indirect
github.com/hyperledger-labs/weaver-dlt-interoperability/sdks/fabric/go-sdk v1.2.3-alpha.1.0.20210812140206-37f430515b8c // indirect
github.com/hyperledger/fabric v1.4.0-rc1.0.20230401164317-bd8e24856939 // indirect
github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect
github.com/hyperledger/fabric-chaincode-go v0.0.0-20220920210243-7bc6fa0dd58b // indirect
github.com/hyperledger/fabric-lib-go v1.0.0 // indirect
github.com/hyperledger/fabric-private-chaincode v0.0.0-20210907122433-d56466264e4d // indirect
github.com/hyperledger/fabric-protos-go v0.2.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/ipfs/boxo v0.8.0-rc1 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.20.0 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kataras/blocks v0.0.7 // indirect
github.com/kataras/golog v0.1.9 // indirect
github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 // indirect
github.com/kataras/pio v0.0.12 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.31.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.22.0 // indirect
github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-nat v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.11.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
github.com/quic-go/webtransport-go v0.5.3 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.10.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/sykesm/zap-logfmt v0.0.4 // indirect
github.com/tdewolff/minify/v2 v2.12.9 // indirect
github.com/tdewolff/parse/v2 v2.6.8 // indirect
github.com/test-go/testify v1.1.4 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/yosssi/ace v0.0.5 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.13.0 // indirect
go.opentelemetry.io/otel/sdk v1.13.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/dig v1.17.0 // indirect
go.uber.org/fx v1.20.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

Some files were not shown because too many files have changed in this diff Show more