se borraron los ejemplos de off_chain_data, hardware security y high throughput

This commit is contained in:
luc662 2025-05-17 21:41:18 +00:00
parent fd3acdb16e
commit 92301573d5
78 changed files with 0 additions and 6316 deletions

View file

@ -1 +0,0 @@
crypto-material

View file

@ -1,122 +0,0 @@
# Hardware Security Module Samples
The samples show how to create client applications that invoke transactions using identity credentials stored in a Hardware Security Module (HSM). When using an HSM, private keys for a Fabric enrollment are stored within a dedicated hardware module. The private keys are not accessible outside of the HSM, and messages are sent to the HSM to be signed.
The samples use the Fabric Gateway client API and will only run against Fabric v2.4 and higher.
## Install prerequisites
### C compilers
In order for the client application to run successfully you must ensure you have C compilers and Python 3 (Note that Python 2 may still work however Python 2 is out of support and could stop working in the future) installed otherwise the node dependency `pkcs11js` will not be built and the application will fail. The failure will have an error such as
```
Error: Cannot find module 'pkcs11js'
```
how to install the required C Compilers and Python will depend on your operating system and version.
### SoftHSM
In order to run the application in the absence of a real HSM, a software emulator of the PKCS#11 interface ([SoftHSM v2](https://www.opendnssec.org/softhsm/)) is required. This can either be:
- installed using the package manager for your host system:
- Ubuntu: `sudo apt install softhsm2`
- macOS: `brew install softhsm`
- Windows: **unsupported**
- or compiled and installed from source, following the [SoftHSM2 install instructions](https://wiki.opendnssec.org/display/SoftHSMDOCS/SoftHSM+Documentation+v2)
- It is recommended to use the `--disable-gost` option unless you need **gost** algorithm support for the Russian market, since it requires additional libraries.
### PKCS#11 enabled fabric-ca-client binary
To be able to register and enroll identities using an HSM you need a PKCS#11 enabled version of `fabric-ca-client`
To install this use the following command
```bash
go install -tags pkcs11 github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest
```
## Create Fabric network and deploy the smart contract
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).
```bash
./network.sh up createChannel -ca
```
2. Deploy one of the smart contract implementations (from the `test-network` folder).
```bash
# To deploy the TypeScript chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
# To deploy the Go chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
# To deploy the Java chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
```
## Initialize a token to store keys in SoftHSM
If you have not initialized a token previously (or it has been deleted) then you will need to perform this one time operation
```bash
mkdir -p "${TMPDIR:-/tmp}/softhsm"
echo "directories.tokendir = ${TMPDIR:-/tmp}/softhsm" > "${HOME}/softhsm2.conf"
SOFTHSM2_CONF="${HOME}/softhsm2.conf" softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234
```
This will create a SoftHSM configuration file called `softhsm2.conf` and will be stored in the home directory. This is
where the sample expects to find a SoftHSM configuration file
The Security Officer PIN, specified with the `--so-pin` flag, can be used to re-initialize the token,
and the user PIN (see below), specified with the `--pin` flag, is used by applications to access the token for
generating and retrieving keys.
## Enroll the HSM User
A user, `HSMUser`, who is HSM managed needs to be registered then enrolled for the sample.
If your PKCS11 library (libsofthsm2.so) is not located in one of the typical system locations checked by this sample's scripts and applications, you will need to explicitly specify the library location using the `PKCS11_LIB` environment variable.
```bash
export PKCS11_LIB='<path to PKCS11 library location>'
```
Register a user `HSMUser` with the CA in Org1 (if not already registered) and then enroll that user which will generate a certificate on the file system for use by the sample. The private key is stored in SoftHSM.
From the `hardware-security-module` folder, run the command:
```bash
SOFTHSM2_CONF="${HOME}/softhsm2.conf" ./scripts/generate-hsm-user.sh HSMUser
```
## Run the sample application
### Go
For HSM support you need to ensure you include the `pkcs11` build tag. From the `hardware-security-module/application-go` folder, run the command:
```
SOFTHSM2_CONF="${HOME}/softhsm2.conf" go run -tags pkcs11 .
```
### Node
From the `hardware-security-module/application-typescript` folder, run the commands:
```
npm install
SOFTHSM2_CONF="${HOME}/softhsm2.conf" npm start
```
## Cleanup
When you are finished running the samples, the local test-network can be brought down with the following command (from the `test-network` folder):
```
./network.sh down
```
Created public credentials can be removed from the filesystem by deleting the `hardware-security-module/crypto-material` folder.
SoftHSM tokens and private credentials stored within them can be removed by deleting the `${TMPDIR:-/tmp}/softhsm` folder.

View file

@ -1,19 +0,0 @@
module github.com/hyperledger/fabric-samples/hardware-security-module/application-go
go 1.21
require (
github.com/hyperledger/fabric-gateway v1.5.0
google.golang.org/grpc v1.63.2
)
require (
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/crypto v0.22.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/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

View file

@ -1,32 +0,0 @@
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hyperledger/fabric-gateway v1.5.0 h1:JChlqtJNm2479Q8YWJ6k8wwzOiu2IRrV3K8ErsQmdTU=
github.com/hyperledger/fabric-gateway v1.5.0/go.mod h1:v13OkXAp7pKi4kh6P6epn27SyivRbljr8Gkfy8JlbtM=
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/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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,215 +0,0 @@
//go:build pkcs11
// +build pkcs11
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"encoding/json"
"encoding/pem"
"errors"
"os"
"crypto/x509"
"fmt"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
mspID = "Org1MSP"
certPath = "../crypto-material/hsm/HSMUser/signcerts/cert.pem"
tlsCertPath = "../../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
peerEndpoint = "dns:///localhost:7051"
)
var now = time.Now()
var assetId = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
func main() {
fmt.Println("Running the GO HSM Sample")
// The gRPC client connection should be shared by all Gateway connections to this endpoint
clientConnection := newGrpcConnection()
defer clientConnection.Close()
hsmSignerFactory, err := identity.NewHSMSignerFactory(findSoftHSMLibrary())
if err != nil {
panic(err)
}
defer hsmSignerFactory.Dispose()
certificatePEM, err := os.ReadFile(certPath)
if err != nil {
panic(err)
}
id := newIdentity(certificatePEM)
ski := getSKI(certificatePEM)
hsmSign, hsmSignClose := newHSMSign(hsmSignerFactory, ski)
defer hsmSignClose()
// Create a Gateway connection for a specific client identity
gateway, err := client.Connect(id, client.WithSign(hsmSign), client.WithClientConnection(clientConnection))
if err != nil {
panic(err)
}
defer gateway.Close()
exampleTransaction(gateway)
fmt.Println()
fmt.Println("Go HSM Sample completed successfully")
fmt.Println()
}
func exampleTransaction(gateway *client.Gateway) {
// Override default values for chaincode and channel name as they may differ in testing contexts.
channelName := "mychannel"
if cname := os.Getenv("CHANNEL_NAME"); cname != "" {
channelName = cname
}
chaincodeName := "basic"
if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" {
chaincodeName = ccname
}
network := gateway.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)
fmt.Printf("Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments \n")
_, err := contract.SubmitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("*** Transaction committed successfully\n")
fmt.Printf("Evaluate Transaction: ReadAsset, function returns asset attributes\n")
evaluateResult, err := contract.EvaluateTransaction("ReadAsset", assetId)
if err != nil {
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
}
result := formatJSON(evaluateResult)
fmt.Printf("*** Result:%s\n", result)
}
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection() *grpc.ClientConn {
certificate, err := loadCertificate(tlsCertPath)
if err != nil {
panic(fmt.Errorf("failed to obtain commit status: %w", err))
}
certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, "peer0.org1.example.com")
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
}
return connection
}
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity(certificatePEM []byte) *identity.X509Identity {
cert, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}
id, err := identity.NewX509Identity(mspID, cert)
if err != nil {
panic(err)
}
return id
}
// newHSMSign creates a function that generates a digital signature from a message digest using a private key.
func newHSMSign(h *identity.HSMSignerFactory, certPEM []byte) (identity.Sign, identity.HSMSignClose) {
opt := identity.HSMSignerOptions{
Label: "ForFabric",
Pin: "98765432",
Identifier: string(certPEM),
}
sign, close, err := h.NewHSMSigner(opt)
if err != nil {
panic(err)
}
return sign, close
}
func loadCertificate(filename string) (*x509.Certificate, error) {
certificatePEM, err := os.ReadFile(filename) //#nosec G304
if err != nil {
return nil, err
}
return identity.CertificateFromPEM(certificatePEM)
}
func getSKI(certPEM []byte) []byte {
block, _ := pem.Decode(certPEM)
x590cert, _ := x509.ParseCertificate(block.Bytes)
pk := x590cert.PublicKey
return skiForKey(pk.(*ecdsa.PublicKey))
}
func skiForKey(pk *ecdsa.PublicKey) []byte {
ski := sha256.Sum256(elliptic.Marshal(pk.Curve, pk.X, pk.Y))
return ski[:]
}
func findSoftHSMLibrary() string {
libraryLocations := []string{
"/usr/lib/softhsm/libsofthsm2.so",
"/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so",
"/usr/local/lib/softhsm/libsofthsm2.so",
"/usr/lib/libacsp-pkcs11.so",
"/opt/homebrew/lib/softhsm/libsofthsm2.so",
}
pkcs11lib := os.Getenv("PKCS11_LIB")
if pkcs11lib != "" {
libraryLocations = append(libraryLocations, pkcs11lib)
}
for _, libraryLocation := range libraryLocations {
if _, err := os.Stat(libraryLocation); !errors.Is(err, os.ErrNotExist) {
return libraryLocation
}
}
panic("No SoftHSM library can be found. The Sample requires SoftHSM to be installed")
}
// Format JSON data
func formatJSON(data []byte) string {
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return prettyJSON.String()
}

View file

@ -1,3 +0,0 @@
dist/
node_modules/
package-lock.json

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": "gateway-hsm-sample",
"version": "0.0.1",
"description": "",
"main": "dist/hsm-sample.js",
"engines": {
"node": ">=18"
},
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"clean": "rimraf dist",
"lint": "eslint src",
"start": "SOFTHSM2_CONF=\"${SOFTHSM2_CONF:-${HOME}/softhsm2.conf}\" node dist/hsm-sample.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.10",
"@hyperledger/fabric-gateway": "^1.5"
},
"devDependencies": {
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.1",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
}
}

View file

@ -1,173 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { connect, Gateway, HSMSigner, HSMSignerFactory, HSMSignerOptions, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import { TextDecoder } from 'util';
const mspId = 'Org1MSP';
const user = 'HSMUser';
const assetId = `asset${String(Date.now())}`;
const utf8Decoder = new TextDecoder();
// Sample uses fabric-ca-client generated HSM identities, certificate is located in the signcerts directory
// and has been stored in a directory of the name given to the identity.
const certPath = path.resolve(__dirname, '..', '..', 'crypto-material', 'hsm', user, 'signcerts', 'cert.pem');
const tlsCertPath = path.resolve(__dirname, '..', '..', '..', 'test-network','organizations','peerOrganizations', 'org1.example.com', 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt');
const peerEndpoint = 'localhost:7051';
async function main() {
console.log('\nRunning the Node HSM sample');
let client;
let gateway;
let hsmSignerFactory;
let hsmSigner;
try {
// The gRPC client connection should be shared by all Gateway connections to this endpoint
client = await newGrpcConnection();
// get an HSMSigner Factory. You only need to do this once for the application
hsmSignerFactory = signers.newHSMSignerFactory(findSoftHSMPKCS11Lib());
const credentials = await fs.promises.readFile(certPath);
// Get the signer function and a close function. The close function closes the signer
// once there is no further need for it.
hsmSigner = newHSMSigner(hsmSignerFactory, credentials);
gateway = connect({
client,
identity: { mspId, credentials },
signer:hsmSigner.signer,
});
await exampleTransaction(gateway);
console.log();
console.log('Node HSM sample completed successfully');
} finally {
gateway?.close();
client?.close();
hsmSigner?.close();
hsmSignerFactory?.dispose();
}
}
async function exampleTransaction(gateway: Gateway):Promise<void> {
const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments');
await contract.submitTransaction(
'CreateAsset',
assetId,
'yellow',
'5',
'Tom',
'1300',
);
console.log('*** Transaction committed successfully');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns asset attributes');
const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId);
const resultJson = utf8Decoder.decode(resultBytes);
const result: unknown = JSON.parse(resultJson);
console.log('*** Result:', result);
}
async function newGrpcConnection(): Promise<grpc.Client> {
const tlsRootCert = await fs.promises.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': 'peer0.org1.example.com'
});
}
// Create a new HSM Signer
function newHSMSigner(hsmSignerFactory: HSMSignerFactory, certificatePEM: Buffer): HSMSigner {
const certificate = new crypto.X509Certificate(certificatePEM);
const ski = getSKIFromCertificate(certificate);
// Options for the signer based on using SoftHSM with Token initialized as follows
// softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234
const hsmSignerOptions: HSMSignerOptions = {
label: 'ForFabric',
pin: '98765432',
identifier: ski
}
return hsmSignerFactory.newSigner(hsmSignerOptions);
}
// Utility to find the SoftHSM PKCS11 library as it's location can vary based on
// operating system and version
function findSoftHSMPKCS11Lib(): string {
const commonSoftHSMPathNames = [
'/usr/lib/softhsm/libsofthsm2.so',
'/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so',
'/usr/local/lib/softhsm/libsofthsm2.so',
'/usr/lib/libacsp-pkcs11.so',
'/opt/homebrew/lib/softhsm/libsofthsm2.so'
];
const pkcs11lib = process.env['PKCS11_LIB'];
if (pkcs11lib) {
commonSoftHSMPathNames.push(pkcs11lib);
}
for (const pathnameToTry of commonSoftHSMPathNames) {
if (fs.existsSync(pathnameToTry)) {
return pathnameToTry
}
}
throw new Error('Unable to find PKCS11 library')
}
// fabric-ca-client set's the CKA_ID of the public/private keys in the HSM to a generated SKI
// value. This function replicates that calculation from a certificate PEM so that the HSM
// object associated with the certificate can be found
function getSKIFromCertificate(certificate: crypto.X509Certificate): Buffer {
const uncompressedPoint = getUncompressedPointOnCurve(certificate.publicKey);
return crypto.createHash('sha256').update(uncompressedPoint).digest();
}
function getUncompressedPointOnCurve(key: crypto.KeyObject): Buffer {
const jwk = key.export({ format: 'jwk' });
const x = Buffer.from(assertDefined(jwk.x), 'base64url');
const y = Buffer.from(assertDefined(jwk.y), 'base64url');
const prefix = Buffer.from('04', 'hex');
return Buffer.concat([prefix, x, y]);
}
function assertDefined<T>(value: T | undefined): T {
if (value === undefined) {
throw new Error('required value was undefined');
}
return value;
}
/**
* envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined.
*/
function envOrDefault(key: string, defaultValue: string): string {
return process.env[key] || defaultValue;
}
main().catch((error: unknown) => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});

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 @@
fabric-ca-client-config.yaml

View file

@ -1,168 +0,0 @@
#############################################################################
# This is a configuration file for the fabric-ca-client command.
#
# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
# ------------------------------------------------
# Each configuration element can be overridden via command line
# arguments or environment variables. The precedence for determining
# the value of each element is as follows:
# 1) command line argument
# Examples:
# a) --url https://localhost:7054
# To set the fabric-ca server url
# b) --tls.client.certfile certfile.pem
# To set the client certificate for TLS
# 2) environment variable
# Examples:
# a) FABRIC_CA_CLIENT_URL=https://localhost:7054
# To set the fabric-ca server url
# b) FABRIC_CA_CLIENT_TLS_CLIENT_CERTFILE=certfile.pem
# To set the client certificate for TLS
# 3) configuration file
# 4) default value (if there is one)
# All default values are shown beside each element below.
#
# FILE NAME ELEMENTS
# ------------------
# The value of all fields whose name ends with "file" or "files" are
# name or names of other files.
# For example, see "tls.certfiles" and "tls.client.certfile".
# The value of each of these fields can be a simple filename, a
# relative path, or an absolute path. If the value is not an
# absolute path, it is interpreted as being relative to the location
# of this configuration file.
#
#############################################################################
#############################################################################
# Client Configuration
#############################################################################
# URL of the Fabric-ca-server (default: http://localhost:7054)
url: http://localhost:7054
# Membership Service Provider (MSP) directory
# This is useful when the client is used to enroll a peer or orderer, so
# that the enrollment artifacts are stored in the format expected by MSP.
mspdir: caadmin
#############################################################################
# TLS section for secure socket connection
#
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
tls:
# TLS section for secure socket connection
certfiles:
client:
certfile:
keyfile:
#############################################################################
# Certificate Signing Request section for generating the CSR for an
# enrollment certificate (ECert)
#
# cn - Used by CAs to determine which domain the certificate is to be generated for
#
# keyrequest - Properties to use when generating a private key.
# algo - key generation algorithm to use
# size - size of key to generate
# reusekey - reuse existing key during reenrollment
#
# serialnumber - The serialnumber field, if specified, becomes part of the issued
# certificate's DN (Distinguished Name). For example, one use case for this is
# a company with its own CA (Certificate Authority) which issues certificates
# to its employees and wants to include the employee's serial number in the DN
# of its issued certificates.
# WARNING: The serialnumber field should not be confused with the certificate's
# serial number which is set by the CA but is not a component of the
# certificate's DN.
#
# names - A list of name objects. Each name object should contain at least one
# "C", "L", "O", or "ST" value (or any combination of these) where these
# are abbreviations for the following:
# "C": country
# "L": locality or municipality (such as city or town name)
# "O": organization
# "OU": organizational unit, such as the department responsible for owning the key;
# it can also be used for a "Doing Business As" (DBS) name
# "ST": the state or province
#
# Note that the "OU" or organizational units of an ECert are always set according
# to the values of the identities type and affiliation. OUs are calculated for an enroll
# as OU=<type>, OU=<affiliationRoot>, ..., OU=<affiliationLeaf>. For example, an identity
# of type "client" with an affiliation of "org1.dept2.team3" would have the following
# organizational units: OU=client, OU=org1, OU=dept2, OU=team3
#
# hosts - A list of host names for which the certificate should be valid
#
#############################################################################
csr:
cn: admin
keyrequest:
algo: ecdsa
size: 256
reusekey: false
serialnumber:
names:
- C: US
ST: North Carolina
L:
O: Hyperledger
OU: Fabric
hosts:
- tryfabric
#############################################################################
# Registration section used to register a new identity with fabric-ca server
#
# name - Unique name of the identity
# type - Type of identity being registered (e.g. 'peer, app, user')
# affiliation - The identity's affiliation
# maxenrollments - The maximum number of times the secret can be reused to enroll.
# Specially, -1 means unlimited; 0 means to use CA's max enrollment
# value.
# attributes - List of name/value pairs of attribute for identity
#############################################################################
id:
name:
type:
affiliation:
maxenrollments: 0
attributes:
# - name:
# value:
#############################################################################
# Enrollment section used to enroll an identity with fabric-ca server
#
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#############################################################################
enrollment:
profile:
label:
#############################################################################
# Name of the CA to connect to within the fabric-ca server
#############################################################################
caname:
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section allows to select which
# crypto implementation library to use
#############################################################################
bccsp:
default: PKCS11
PKCS11:
Library: REPLACE_ME_HSMLIB
Pin: 98765432
Label: ForFabric
hash: SHA2
security: 256

View file

@ -1,82 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
# script directory
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# define the CA setup
CA_HOST=localhost
CA_URL="${CA_HOST}:7054"
TLS_CERT="${SCRIPT_DIR}/../../test-network/organizations/fabric-ca/org1/tls-cert.pem"
export SOFTHSM2_CONF="${SOFTHSM2_CONF:-${HOME}/softhsm2.conf}"
LocateHsmLib() {
local POSSIBLE_LIB_LOC=( \
'/usr/lib/softhsm/libsofthsm2.so' \
'/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so' \
'/usr/local/lib/softhsm/libsofthsm2.so' \
'/usr/lib/libacsp-pkcs11.so' \
'/opt/homebrew/lib/softhsm/libsofthsm2.so' \
)
for TEST_LIB in "${POSSIBLE_LIB_LOC[@]}"; do
if [ -f "${TEST_LIB}" ]; then
echo "${TEST_LIB}"
return
fi
done
}
HSM2_LIB="${PKCS11_LIB:-$(LocateHsmLib)}"
[ -z "${HSM2_LIB}" ] && echo No SoftHSM PKCS11 Library found, ensure you have installed softhsm2 && exit 1
# create a softhsm2.conf file if one doesn't exist
if [ ! -f "${SOFTHSM2_CONF}" ]; then
TMPDIR="${TMPDIR:-/tmp}"
mkdir -p "${TMPDIR}/softhsm"
echo "directories.tokendir = ${TMPDIR}/softhsm" > "${SOFTHSM2_CONF}"
fi
softhsm2-util --init-token --slot 0 --label 'ForFabric' --pin 98765432 --so-pin 1234 || true
# Update the client config file to point to the softhsm pkcs11 library
# which must be in $HOME/softhsm directory
CLIENT_CONFIG_TEMPLATE="${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config-template.yaml"
CLIENT_CONFIG="${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config.yaml"
CLIENT_CONFIG_CONTENT="$( sed "s+REPLACE_ME_HSMLIB+${HSM2_LIB}+g" "${CLIENT_CONFIG_TEMPLATE}" )"
echo "${CLIENT_CONFIG_CONTENT}" > "${CLIENT_CONFIG}"
# create the users, remove any existing users
CRYPTO_PATH="${SCRIPT_DIR}/../crypto-material/hsm"
[ -d "${CRYPTO_PATH}" ] && rm -fr "${CRYPTO_PATH}"
# user passed in as parameter
CAADMIN="admin"
CAADMIN_PW="adminpw"
HSMUSER="$1"
fabric-ca-client enroll \
-c "${CLIENT_CONFIG}" \
-u "https://${CAADMIN}:${CAADMIN_PW}@${CA_URL}" \
--mspdir "${CRYPTO_PATH}/${CAADMIN}" \
--tls.certfiles "${TLS_CERT}"
! fabric-ca-client register \
-c "${CLIENT_CONFIG}" \
--mspdir "${CRYPTO_PATH}/${CAADMIN}" \
--id.name "${HSMUSER}" \
--id.secret "${HSMUSER}" \
--id.type client \
--caname ca-org1 \
--id.maxenrollments 0 \
-m example.com \
-u "https://${CA_URL}" \
--tls.certfiles "${TLS_CERT}" \
&& echo user probably already registered, continuing
fabric-ca-client enroll \
-c "${CLIENT_CONFIG}" \
-u "https://${HSMUSER}:${HSMUSER}@${CA_URL}" \
--mspdir "${CRYPTO_PATH}/${HSMUSER}" \
--tls.certfiles "${TLS_CERT}"

View file

@ -1,201 +0,0 @@
<!--
Copyright IBM Corp All Rights Reserved
SPDX-License-Identifier: Apache-2.0
-->
# High-Throughput Network
## Purpose
This network is used to understand how to properly design the chaincode data model when handling thousands of transactions per second which all
update the same asset in the ledger. A naive implementation would use a single key to represent the data for the asset, and the chaincode would
then attempt to update this key every time a transaction involving it comes in. However, when many transactions all come in at once, in the time
between when the transaction is simulated on the peer (i.e. read-set is created) and it's ready to be committed to the ledger, another transaction
may have already updated the same value. Thus, in the simple implementation, the read-set version will no longer match the version in the orderer,
and a large number of parallel transactions will fail. To solve this issue, the frequently updated value is instead stored as a series of deltas
which are aggregated when the value must be retrieved. In this way, no single row is frequently read and updated, but rather a collection of rows
is considered.
## Use Case
The primary use case for this chaincode data model design is for applications in which a particular asset has an associated amount that is
frequently added to or removed from. For example, with a bank or credit card account, money is either paid to or paid out of it, and the amount
of money in the account is the result of all of these additions and subtractions aggregated together. A typical person's bank account may not be
used frequently enough to require highly-parallel throughput, but an organizational account used to store the money collected from customers on an
e-commerce platform may very well receive a very high number of transactions from all over the world all at once. In fact, this use case is the only
use case for crypto currencies like Bitcoin: a user's unspent transaction output (UTXO) is the result of all transactions he or she has been a part of
since joining the blockchain. Other use cases that can employ this technique might be IOT sensors which frequently update their sensed value in the
cloud.
By adopting this method of storing data, an organization can optimize their chaincode to store and record transactions as quickly as possible and can
aggregate ledger records into one value at the time of their choosing without sacrificing transaction performance. Given the state-machine design of
Hyperledger Fabric, however, careful considerations need to be given to the data model design for the chaincode.
Let's look at some concrete use cases and how an organization might implement high-throughput storage. These cases will try and explore some of the
advantages and disadvantages of such a system, and how to overcome them.
#### Example 1 (IOT): Boxer Construction Analysts
Boxer Construction Analysts is an IOT company focused on enabling real-time monitoring of large, expensive assets (machinery) on commercial
construction projects. They've partnered with the only construction vehicle company in New York, Condor Machines Inc., to provide a reliable,
auditable, and replayable monitoring system on their machines. This allows Condor to monitor their machines and address problems as soon as
they occur while providing end-users with a transparent report on machine health, which helps keep the customers satisfied.
The vehicles are outfitted with many sensors each of which broadcasts updated values at frequencies ranging from several times a second to
several times a minute. Boxer initially sets up their chaincode so that the central machine computer pushes these values out to the blockchain
as soon as they're produced, and each sensor has its own row in the ledger which is updated when a new value comes in. While they find that
this works fine for the sensors which only update several times a minute, they run into some issues when updating the faster sensors. Often,
the blockchain skips several sensor readings before adding a new one, defeating the purpose of having a fast, always-on sensor. The issue they're
running into is that they're sending update transactions so fast that the version of the row is changed between the creation of a transaction's
read-set and committing that transaction to the ledger. The result is that while a transaction is in the process of being committed, all future
transactions are rejected until the commitment process is complete and a new, much later reading updates the ledger.
To address this issue, they adopt a high-throughput design for the chaincode data model instead. Each sensor has a key which identifies it within the
ledger, and the difference between the previous reading and the current reading is published as a transaction. For example, if a sensor is monitoring
engine temperature, rather than sending the following list: 220F, 223F, 233F, 227F, the sensor would send: +220, +3, +10, -6 (the sensor is assumed
to start a 0 on initialization). This solves the throughput problem, as the machine can post delta transactions as fast as it wants and they will all
eventually be committed to the ledger in the order they were received. Additionally, these transactions can be processed as they appear in the ledger
by a dashboard to provide live monitoring data. The only difference the engineers have to pay attention to in this case is to make sure the sensors can
send deltas from the previous reading, rather than fixed readings.
#### Example 2 (Balance Transfer): Robinson Credit Co.
Robinson Credit Co. provides credit and financial services to large businesses. As such, their accounts are large, complex, and accessed by many
people at once at any time of the day. They want to switch to blockchain, but are having trouble keeping up with the number of deposits and
withdrawals happening at once on the same account. Additionally, they need to ensure users never withdraw more money than is available
on an account, and transactions that do get rejected. The first problem is easy to solve, the second is more nuanced and requires a variety of
strategies to accommodate high-throughput storage model design.
To solve throughput, this new storage model is leveraged to allow every user performing transactions against the account to make that transaction in terms
of a delta. For example, global e-commerce company America Inc. must be able to accept thousands of transactions an hour in order to keep up with
their customer's demands. Rather than attempt to update a single row with the total amount of money in America Inc's account, Robinson Credit Co.
accepts each transaction as an additive delta to America Inc's account. At the end of the day, America Inc's accounting department can quickly
retrieve the total value in the account when the sums are aggregated.
However, what happens when American Inc. now wants to pay its suppliers out of the same account, or a different account also on the blockchain?
Robinson Credit Co. would like to be assured that America Inc.'s accounting department can't simply overdraw their account, which is difficult to
do while at the same enabling transactions to happen quickly, as deltas are added to the ledger without any sort of bounds checking on the final
aggregate value. There are a variety of solutions which can be used in combination to address this.
Solution 1 involves polling the aggregate value regularly. This happens separate from any delta transaction, and can be performed by a monitoring
service setup by Robinson themselves so that they can at least be guaranteed that if an overdraw does occur, they can detect it within a known
number of seconds and respond to it appropriately (e.g. by temporarily shutting off transactions on that account), all of which can be automated.
Furthermore, thanks to the decentralized nature of Fabric, this operation can be performed on a peer dedicated to this function that would not
slow down or impact the performance of peers processing customer transactions.
Solution 2 involves breaking up the submission and verification steps of the balance transfer. Balance transfer submissions happen very quickly
and don't bother with checking overdrawing. However, a secondary process reviews each transaction sent to the chain and keeps a running total,
verifying that none of them overdraw the account, or at the very least that aggregated withdrawals vs deposits balance out at the end of the day.
Similar to Solution 1, this system would run separate from any transaction processing hardware and would not incur a performance hit on the
customer-facing chain.
Solution 3 involves individually tailoring the smart contracts between Robinson and America Inc, leveraging the power of chaincode to customize
spending limits based on solvency proofs. Perhaps a limit is set on withdrawal transactions such that anything below \$1000 is automatically processed
and assumed to be correct and at minimal risk to either company simply due to America Inc. having proved solvency. However, withdrawals above \$1000
must be verified before approval and admittance to the chain.
## How
This sample provides the chaincode and scripts required to run a high-throughput application on the Fabric test network.
### Start the network
You can use the `startFabric.sh` script to create an instance of the Fabric test network with a single channel named `mychannel`. The script then deploys the `high-throughput` chaincode to the channel by installing it on the test network peers and committing the chaincode definition to the channel.
Change back into the `high-throughput` directory in `fabic-samples`. Start the network and deploy the chaincode by issuing the following command:
```
./startFabric.sh
```
If successful, you will see messages of the Fabric test network being created and the chaincode being deployed, followed by the execution time of the script:
```
Total setup execution time : 81 secs ...
```
The `high-throughput` chaincode is now ready to receive invocations.
### Invoke the chaincode
You can invoke the `high-througput` chaincode using a Go application in the `application-go` folder. The Go application will allow us to submit many transactions to the network concurrently. Navigate to the application:
```
cd application-go
```
#### Update
The format for update is: `go run app.go update name value operation` where `name` is the name of the variable to update, `value` is the value to add to the variable, and `operation` is either `+` or `-` depending on what type of operation you'd like to add to the variable.
Example: `go run app.go update myvar 100 +`
#### Query
You can query the value of a variable by running `go run app.go get name` where `name` is the name of the variable to get.
Example: `go run app.go get myvar`
#### Prune
Pruning takes all the deltas generated for a variable and combines them all into a single row, deleting all previous rows. This helps cleanup the ledger when many updates have been performed.
The format for pruning is: `go run app.go prune name` where `name` is the name of the variable to prune.
Example: `go run app.go prune myvar`
#### Delete
The format for delete is: `go run app.go delete name` where `name` is the name of the variable to delete.
Example: `go run app.go delete myvar`
### Test the Network
The application provides two methods that demonstrate the advantages of this system by submitting many concurrent transactions to the smart contract: `manyUpdates` and `manyUpdatesTraditional`. The first function accepts the same arguments as `update-invoke.sh` but runs the invocation 1000 times in parallel. The final value, therefore, should be the given update value * 1000.
The second function, `manyUpdatesTraditional`, submits 1000 transactions that attempt to update the same key in the world state 1000 times.
Run the following command to create and update `testvar1` a 1000 times:
```
go run app.go manyUpdates testvar1 100 +
```
The application will query the variable after submitting the transaction. The result should be `100000`.
We will now see what happens when you try to run 1000 concurrent updates using a traditional transaction. Run the following command to create a variable named `testvar2`:
```
go run app.go update testvar2 100 +
```
The variable will have a value of 100:
```
2020/10/27 18:01:45 Value of variable testvar2 : 100
```
Now lets try to update `testvar2` 1000 times in parallel:
```
go run app.go manyUpdatesTraditional testvar2 100 +
```
When the program ends, you may see that none of the updates succeeded.
```
2020/10/27 18:03:15 Final value of variable testvar2 : 100
```
The transactions failed because multiple transactions in each block updated the same key. Because of these transactions generated read/write conflicts, the transactions included in each block were rejected in the validation stage.
You can can examine the peer logs to view the messages generated by the rejected blocks:
```
docker logs peer0.org1.example.com
```
```
[...]
2020-10-28 17:37:58.746 UTC [gossip.privdata] StoreBlock -> INFO 2190 [mychannel] Received block [407] from buffer
2020-10-28 17:37:58.749 UTC [committer.txvalidator] Validate -> INFO 2191 [mychannel] Validated block [407] in 2ms
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2192 Block [407] Transaction index [0] TxId [b6b14cf988b0d7d35d4e0d7a0d2ae0c9f5569bc10ec5010f03a28c22694b8ef6] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2193 Block [407] Transaction index [1] TxId [9d7c4f6ff95a0f22e01d6ffeda261227752e78db43f2673ad4ea6f0fdace44d1] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2194 Block [407] Transaction index [2] TxId [9cc228b61d8841208feb6160254aee098b1b3a903f645e62cfa12222e6f52e65] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2195 Block [407] Transaction index [3] TxId [2ae78d363c30b5f3445f2b028ccac7cf821f1d5d5c256d8c17bd42f33178e2ed] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
```
### Clean up
When you are finished using the `high-throughput` chaincode, you can bring down the network and remove any accompanying artifacts using the `networkDown.sh` script.
```
./networkDown.sh
```

View file

@ -1,4 +0,0 @@
wallet
!wallet/.gitkeep
keystore

View file

@ -1,70 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"os"
f "github.com/hyperledger/fabric-samples/high-throughput/application-go/functions"
)
func main() {
var function, variableName, change, sign string
if len(os.Args) <= 2 {
log.Println("Usage: function variableName")
log.Fatalf("functions: update manyUpdates manyUpdatesTraditional get prune delete")
} else if (os.Args[1] == "update" || os.Args[1] == "manyUpdates" || os.Args[1] == "manyUpdatesTraditional") && len(os.Args) < 5 {
log.Fatalf("error: provide value and operation")
} else if len(os.Args) == 3 {
function = os.Args[1]
variableName = os.Args[2]
} else if len(os.Args) == 5 {
function = os.Args[1]
variableName = os.Args[2]
change = os.Args[3]
sign = os.Args[4]
}
// Handle different functions
if function == "update" {
result, err := f.Update(function, variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Value of variable", string(variableName), ": ", string(result))
} else if function == "delete" || function == "prune" || function == "delstandard" {
result, err := f.DeletePrune(function, variableName)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println(string(result))
} else if function == "get" || function == "getstandard" {
result, err := f.Query(function, variableName)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Value of variable", string(variableName), ": ", string(result))
} else if function == "manyUpdates" {
log.Println("submitting 1000 concurrent updates...")
result, err := f.ManyUpdates("update", variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Final value of variable", string(variableName), ": ", string(result))
} else if function == "manyUpdatesTraditional" {
log.Println("submitting 1000 concurrent updates...")
result, err := f.ManyUpdates("putstandard", variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Final value of variable", string(variableName), ": ", string(result))
}
}

View file

@ -1,69 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// DeletePrune deletes or prunes a variable
func DeletePrune(function, variableName string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.SubmitTransaction(function, variableName)
if err != nil {
return result, fmt.Errorf("failed to Submit transaction: %v", err)
}
return result, err
}

View file

@ -1,86 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"sync"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// ManyUpdates allows you to push many cuncurrent updates to a variable
func ManyUpdates(function, variableName, change, sign string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() ([]byte, error) {
defer wg.Done()
result, err := contract.SubmitTransaction(function, variableName, change, sign)
if err != nil {
return result, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, nil
}()
}
wg.Wait()
result, err := contract.EvaluateTransaction("get", variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -1,69 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// Query can be used to read the latest value of a variable
func Query(function, variableName string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err = populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.EvaluateTransaction(function, variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -1,74 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// Update can be used to update or prune the variable
func Update(function, variableName, change, sign string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.SubmitTransaction(function, variableName, change, sign)
if err != nil {
return result, fmt.Errorf("failed to Submit transaction: %v", err)
}
result, err = contract.EvaluateTransaction("get", variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -1,55 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
func populateWallet(wallet *gateway.Wallet) error {
credPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"users",
"User1@org1.example.com",
"msp",
)
certPath := filepath.Join(credPath, "signcerts", "cert.pem")
// read the certificate pem
cert, err := os.ReadFile(filepath.Clean(certPath))
if err != nil {
return err
}
keyDir := filepath.Join(credPath, "keystore")
// there's a single file in this dir containing the private key
files, err := os.ReadDir(keyDir)
if err != nil {
return err
}
if len(files) != 1 {
return fmt.Errorf("keystore folder should have contain one file")
}
keyPath := filepath.Join(keyDir, files[0].Name())
key, err := os.ReadFile(filepath.Clean(keyPath))
if err != nil {
return err
}
identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))
return wallet.Put("appUser", identity)
}

View file

@ -1,50 +0,0 @@
module github.com/hyperledger/fabric-samples/high-throughput/application-go
go 1.18
require github.com/hyperledger/fabric-sdk-go v1.0.0
require (
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cloudflare/cfssl v1.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/go-kit/kit v0.8.0 // indirect
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/golang/mock v1.4.3 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/google/certificate-transparency-go v1.0.21 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hyperledger/fabric-config v0.0.5 // indirect
github.com/hyperledger/fabric-lib-go v1.0.0 // indirect
github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/pelletier/go-toml v1.8.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.1.0 // indirect
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
github.com/prometheus/common v0.6.0 // indirect
github.com/prometheus/procfs v0.0.3 // indirect
github.com/spf13/afero v1.3.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.1.1 // indirect
github.com/stretchr/testify v1.5.1 // indirect
github.com/weppos/publicsuffix-go v0.5.0 // indirect
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e // indirect
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect
google.golang.org/grpc v1.29.1 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)

View file

@ -1,239 +0,0 @@
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw=
github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperledger/fabric-config v0.0.5 h1:khRkm8U9Ghdg8VmZfptgzCFlCzrka8bPfUkM+/j6Zlg=
github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8=
github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324=
github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc=
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 h1:SEbB3yH4ISTGRifDamYXAst36gO2kM855ndMJlsv+pc=
github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/hyperledger/fabric-sdk-go v1.0.0 h1:NRu0iNbHV6u4nd9jgYghAdA1Ll4g0Sri4hwMEGiTbyg=
github.com/hyperledger/fabric-sdk-go v1.0.0/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA=
github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.1.1 h1:/8JBRFO4eoHu1TmpsLgNBq1CQgRUg4GolYlEFieqJgo=
github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -1,8 +0,0 @@
module github.com/hyperledger/fabric-samples/high-throughput/chaincode
go 1.12
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a
github.com/hyperledger/fabric-protos-go v0.3.0
)

File diff suppressed because it is too large Load diff

View file

@ -1,396 +0,0 @@
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*
* Demonstrates how to handle data in an application with a high transaction volume where the transactions
* all attempt to change the same key-value pair in the ledger. Such an application will have trouble
* as multiple transactions may read a value at a certain version, which will then be invalid when the first
* transaction updates the value to a new version, thus rejecting all other transactions until they're
* re-executed.
* Rather than relying on serialization of the transactions, which is slow, this application initializes
* a value and then accepts deltas of that value which are added as rows to the ledger. The actual value
* is then an aggregate of the initial value combined with all of the deltas. Additionally, a pruning
* function is provided which aggregates and deletes the deltas to update the initial value. This should
* be done during a maintenance window or when there is a lowered transaction volume, to avoid the proliferation
* of millions of rows of data.
*
* @author Alexandre Pauwels for IBM
* @created 17 Aug 2017
*/
package main
/* Imports
* 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
* 2 specific Hyperledger Fabric specific libraries for Smart Contracts
*/
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
)
// SmartContract is the data structure which represents this contract and on which various contract lifecycle functions are attached
type SmartContract struct {
}
// Define Status codes for the response
const (
OK = 200
ERROR = 500
)
// Init is called when the smart contract is instantiated
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
// Invoke routes invocations to the appropriate function in chaincode
// Current supported invocations are:
// - update, adds a delta to an aggregate variable in the ledger, all variables are assumed to start at 0
// - get, retrieves the aggregate value of a variable in the ledger
// - prune, deletes all rows associated with the variable and replaces them with a single row containing the aggregate value
// - delete, removes all rows associated with the variable
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) pb.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "update" {
return s.update(APIstub, args)
} else if function == "get" {
return s.get(APIstub, args)
} else if function == "prune" {
return s.prune(APIstub, args)
} else if function == "delete" {
return s.delete(APIstub, args)
} else if function == "putstandard" {
return s.putStandard(APIstub, args)
} else if function == "getstandard" {
return s.getStandard(APIstub, args)
} else if function == "delstandard" {
return s.delStandard(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
/**
* Updates the ledger to include a new delta for a particular variable. If this is the first time
* this variable is being added to the ledger, then its initial value is assumed to be 0. The arguments
* to give in the args array are as follows:
* - args[0] -> name of the variable
* - args[1] -> new delta (float)
* - args[2] -> operation (currently supported are addition "+" and subtraction "-")
*
* @param APIstub The chaincode shim
* @param args The arguments array for the update invocation
*
* @return A response structure indicating success or failure with a message
*/
func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
// Check we have a valid number of args
if len(args) != 3 {
return shim.Error("Incorrect number of arguments, expecting 3")
}
// Extract the args
name := args[0]
op := args[2]
_, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return shim.Error("Provided value was not a number")
}
// Make sure a valid operator is provided
if op != "+" && op != "-" {
return shim.Error(fmt.Sprintf("Operator %s is unrecognized", op))
}
// Retrieve info needed for the update procedure
txid := APIstub.GetTxID()
compositeIndexName := "varName~op~value~txID"
// Create the composite key that will allow us to query for all deltas on a particular variable
compositeKey, compositeErr := APIstub.CreateCompositeKey(compositeIndexName, []string{name, op, args[1], txid})
if compositeErr != nil {
return shim.Error(fmt.Sprintf("Could not create a composite key for %s: %s", name, compositeErr.Error()))
}
// Save the composite key index
compositePutErr := APIstub.PutState(compositeKey, []byte{0x00})
if compositePutErr != nil {
return shim.Error(fmt.Sprintf("Could not put operation for %s in the ledger: %s", name, compositePutErr.Error()))
}
return shim.Success([]byte(fmt.Sprintf("Successfully added %s%s to %s", op, args[1], name)))
}
/**
* Retrieves the aggregate value of a variable in the ledger. Gets all delta rows for the variable
* and computes the final value from all deltas. The args array for the invocation must contain the
* following argument:
* - args[0] -> The name of the variable to get the value of
*
* @param APIstub The chaincode shim
* @param args The arguments array for the get invocation
*
* @return A response structure indicating success or failure with a message
*/
func (s *SmartContract) get(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
// Check we have a valid number of args
if len(args) != 1 {
return shim.Error("Incorrect number of arguments, expecting 1")
}
name := args[0]
// Get all deltas for the variable
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
if deltaErr != nil {
return shim.Error(fmt.Sprintf("Could not retrieve value for %s: %s", name, deltaErr.Error()))
}
defer deltaResultsIterator.Close()
// Check the variable existed
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
}
// Iterate through result set and compute final value
var finalVal float64
var i int
for i = 0; deltaResultsIterator.HasNext(); i++ {
// Get the next row
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(nextErr.Error())
}
// Split the composite key into its component parts
_, keyParts, splitKeyErr := APIstub.SplitCompositeKey(responseRange.Key)
if splitKeyErr != nil {
return shim.Error(splitKeyErr.Error())
}
// Retrieve the delta value and operation
operation := keyParts[1]
valueStr := keyParts[2]
// Convert the value string and perform the operation
value, convErr := strconv.ParseFloat(valueStr, 64)
if convErr != nil {
return shim.Error(convErr.Error())
}
switch operation {
case "+":
finalVal += value
case "-":
finalVal -= value
default:
return shim.Error(fmt.Sprintf("Unrecognized operation %s", operation))
}
}
return shim.Success([]byte(strconv.FormatFloat(finalVal, 'f', -1, 64)))
}
/**
* Prunes a variable by deleting all of its delta rows while computing the final value. Once all rows
* have been processed and deleted, a single new row is added which defines a delta containing the final
* computed value of the variable. The args array contains the following argument:
* - args[0] -> The name of the variable to prune
*
* @param APIstub The chaincode shim
* @param args The args array for the prune invocation
*
* @return A response structure indicating success or failure with a message
*/
func (s *SmartContract) prune(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
// Check we have a valid number of ars
if len(args) != 1 {
return shim.Error("Incorrect number of arguments, expecting 1")
}
// Retrieve the name of the variable to prune
name := args[0]
// Get all delta rows for the variable
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
if deltaErr != nil {
return shim.Error(fmt.Sprintf("Could not retrieve value for %s: %s", name, deltaErr.Error()))
}
defer deltaResultsIterator.Close()
// Check the variable existed
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
}
// Iterate through result set computing final value while iterating and deleting each key
var finalVal float64
var i int
for i = 0; deltaResultsIterator.HasNext(); i++ {
// Get the next row
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(nextErr.Error())
}
// Split the key into its composite parts
_, keyParts, splitKeyErr := APIstub.SplitCompositeKey(responseRange.Key)
if splitKeyErr != nil {
return shim.Error(splitKeyErr.Error())
}
// Retrieve the operation and value
operation := keyParts[1]
valueStr := keyParts[2]
// Convert the value to a float
value, convErr := strconv.ParseFloat(valueStr, 64)
if convErr != nil {
return shim.Error(convErr.Error())
}
// Delete the row from the ledger
deltaRowDelErr := APIstub.DelState(responseRange.Key)
if deltaRowDelErr != nil {
return shim.Error(fmt.Sprintf("Could not delete delta row: %s", deltaRowDelErr.Error()))
}
// Add the value of the deleted row to the final aggregate
switch operation {
case "+":
finalVal += value
case "-":
finalVal -= value
default:
return shim.Error(fmt.Sprintf("Unrecognized operation %s", operation))
}
}
// Update the ledger with the final value
updateResp := s.update(APIstub, []string{name, strconv.FormatFloat(finalVal, 'f', -1, 64), "+"})
if updateResp.Status == ERROR {
return shim.Error(fmt.Sprintf("Could not update the final value of the variable after pruning: %s", updateResp.Message))
}
return shim.Success([]byte(fmt.Sprintf("Successfully pruned variable %s, final value is %f, %d rows pruned", args[0], finalVal, i)))
}
/**
* Deletes all rows associated with an aggregate variable from the ledger. The args array
* contains the following argument:
* - args[0] -> The name of the variable to delete
*
* @param APIstub The chaincode shim
* @param args The arguments array for the delete invocation
*
* @return A response structure indicating success or failure with a message
*/
func (s *SmartContract) delete(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
// Check there are a correct number of arguments
if len(args) != 1 {
return shim.Error("Incorrect number of arguments, expecting 1")
}
// Retrieve the variable name
name := args[0]
// Delete all delta rows
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
if deltaErr != nil {
return shim.Error(fmt.Sprintf("Could not retrieve delta rows for %s: %s", name, deltaErr.Error()))
}
defer deltaResultsIterator.Close()
// Ensure the variable exists
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
}
// Iterate through result set and delete all indices
var i int
for i = 0; deltaResultsIterator.HasNext(); i++ {
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(fmt.Sprintf("Could not retrieve next delta row: %s", nextErr.Error()))
}
deltaRowDelErr := APIstub.DelState(responseRange.Key)
if deltaRowDelErr != nil {
return shim.Error(fmt.Sprintf("Could not delete delta row: %s", deltaRowDelErr.Error()))
}
}
return shim.Success([]byte(fmt.Sprintf("Deleted %s, %d rows removed", name, i)))
}
/**
* Converts a float64 to a byte array
*
* @param f The float64 to convert
*
* @return The byte array representation
*/
func f2barr(f float64) []byte {
str := strconv.FormatFloat(f, 'f', -1, 64)
return []byte(str)
}
// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
/**
* All functions below this are for testing traditional editing of a single row
*/
func (s *SmartContract) putStandard(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
name := args[0]
valStr := args[1]
_, getErr := APIstub.GetState(name)
if getErr != nil {
return shim.Error(fmt.Sprintf("Failed to retrieve the state of %s: %s", name, getErr.Error()))
}
putErr := APIstub.PutState(name, []byte(valStr))
if putErr != nil {
return shim.Error(fmt.Sprintf("Failed to put state: %s", putErr.Error()))
}
return shim.Success(nil)
}
func (s *SmartContract) getStandard(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
name := args[0]
val, getErr := APIstub.GetState(name)
if getErr != nil {
return shim.Error(fmt.Sprintf("Failed to get state: %s", getErr.Error()))
}
return shim.Success(val)
}
func (s *SmartContract) delStandard(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
name := args[0]
getErr := APIstub.DelState(name)
if getErr != nil {
return shim.Error(fmt.Sprintf("Failed to delete state: %s", getErr.Error()))
}
return shim.Success(nil)
}

View file

@ -1,17 +0,0 @@
#!/bin/bash
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
# Exit on first error
set -ex
# Bring the test network down
pushd ../test-network
./network.sh down
popd
rm -rf application-go/wallet/
rm -rf application-go/keystore/

View file

@ -1,28 +0,0 @@
#!/bin/bash
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
# Exit on first error
set -e
# don't rewrite paths for Windows Git Bash users
export MSYS_NO_PATHCONV=1
starttime=$(date +%s)
export TIMEOUT=10
export DELAY=3
# launch network; create channel and join peer to channel
pushd ../test-network
./network.sh down
echo "Bring up test network"
./network.sh up createChannel -ca
./network.sh deployCC -ccn bigdatacc -ccp ../high-throughput/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cci Init
popd
cat <<EOF
Total setup execution time : $(($(date +%s) - starttime)) secs ...
EOF

View file

@ -1,115 +0,0 @@
# Off-chain data store sample
The off-chain data store sample demonstrates:
- Receiving block events in a client application.
- Using a checkpointer to resume event listening after a failure or application restart.
- Extracting ledger updates from block events in order to build an off-chain data store.
## About the sample
This sample shows how to replicate the data in your blockchain network to an off-chain data store. Using an off-chain data store allows you to analyze the data from your network or build a dashboard without degrading the performance of your application.
This sample uses the block event listening capability of the [Fabric Gateway client API](https://hyperledger.github.io/fabric-gateway/) for Fabric v2.4 and later.
### Application
The client application provides several "commands" that can be invoked using the command-line:
- **getAllAssets**: Retrieve the current details of all assets recorded on the ledger. See:
- TypeScript: [application-typescript/src/getAllAssets.ts](application-typescript/src/getAllAssets.ts)
- Java: [application-java/app/src/main/java/GetAllAssets.java](application-java/app/src/main/java/GetAllAssets.java)
- **listen**: Listen for block events, and use them to replicate ledger updates in an off-chain data store. See:
- TypeScript: [application-typescript/src/listen.ts](application-typescript/src/listen.ts)
- Java: [application-java/app/src/main/java/Listen.java](application-java/app/src/main/java/Listen.java)
- **transact**: Submit a set of transactions to create, modify and delete assets. See:
- TypeScript: [application-typescript/src/transact.ts](application-typescript/src/transact.ts)
- Java: [application-java/app/src/main/java/Transact.java](application-java/app/src/main/java/Transact.java)
To keep the sample code concise, the **listen** command writes ledger updates to an output file named `store.log` in the current working directory (which for the Java sample is the `application-java/app` directory). A real implementation could write ledger updates directly to an off-chain data store of choice. You can inspect the information captured in this file as you run the sample.
Note that the **listen** command is is restartable and will resume event listening after the last successfully processed block / transaction. This is achieved using a checkpointer to persist the current listening position. Checkpoint state is persisted to a file named `checkpoint.json` in the current working directory. If no checkpoint state is present, event listening begins from the start of the ledger (block number zero).
### Smart Contract
The asset-transfer-basic smart contract is used to generate transactions and associated ledger updates.
## Running the sample
The Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```bash
./network.sh up createChannel -c mychannel -ca
```
1. Deploy one of the asset-transfer-basic smart contract implementations (from the `test-network` folder).
```bash
# To deploy the TypeScript chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
# To deploy the Go chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
# To deploy the Java chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
```
1. Populate the ledger with some assets and use eventing to capture ledger updates (from the `off_chain_data` folder).
```bash
# To run the TypeScript sample application
cd application-typescript
npm install
npm start transact listen
# To run the Java sample application
cd application-java
./gradlew run --quiet --args='transact listen'
```
1. Interrupt the listener process using **Control-C**.
1. View the current world state of the blockchain (from the `off_chain_data` folder). You may want to compare the results to the ledger updates captured by the listener in the `store.log` file.
```bash
# To run the TypeScript sample application
cd application-typescript
npm --silent start getAllAssets
# To run the Java sample application
cd application-java
./gradlew run --quiet --args=getAllAssets
```
1. Make some more ledger updates, then observe listener resume capability (from the `off_chain_data` folder). Note from the transaction IDs recorded to the console that the listener resumes from exactly after the last successfully processed transaction.
```bash
# To run the TypeScript sample application
cd application-typescript
npm start transact
SIMULATED_FAILURE_COUNT=5 npm start listen
npm start listen
# To run the Java sample application
cd application-java
./gradlew run --quiet --args=transact
SIMULATED_FAILURE_COUNT=5 ./gradlew run --quiet --args=listen
./gradlew run --quiet --args=listen
```
1. Interrupt the listener process using **Control-C**.
## Clean up
The persisted event checkpoint position can be removed by deleting the `checkpoint.json` file while the listener is stopped.
The recorded ledger updates can be removed by deleting the `store.log` file.
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. Be sure to remove the `checkpoint.json` and `store.log` files before attempting to run the application with a new network.
```
./network.sh down
```

View file

@ -1,12 +0,0 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# IntelliJ IDEA files
.idea
# Files generated by the application at runtime
checkpoint.json
store.log

View file

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

View file

@ -1,36 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id 'application' // Support for building a CLI application in Java.
id 'checkstyle'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.hyperledger.fabric:fabric-gateway:1.4.0'
implementation 'org.hyperledger.fabric:fabric-protos:0.2.1'
compileOnly 'io.grpc:grpc-api:1.59.0'
runtimeOnly 'io.grpc:grpc-netty-shaded:1.59.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
checkstyle {
toolVersion '10.3'
}
application {
mainClass = 'App'
}

View file

@ -1,76 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public final class App {
private static final long SHUTDOWN_TIMEOUT_SECONDS = 3;
private static final Map<String, Command> COMMANDS = Map.ofEntries(
Map.entry("getAllAssets", new GetAllAssets()),
Map.entry("transact", new Transact()),
Map.entry("listen", new Listen())
);
private final List<String> commandNames;
private final PrintStream out = System.out;
App(final String[] args) {
commandNames = List.of(args);
}
public void run() throws Exception {
var commands = getCommands();
var grpcChannel = Connections.newGrpcConnection();
try {
for (var command : commands) {
command.run(grpcChannel);
}
} finally {
grpcChannel.shutdownNow().awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
}
private List<Command> getCommands() {
var commands = commandNames.stream()
.map(name -> {
var command = COMMANDS.get(name);
if (command == null) {
printUsage();
throw new IllegalArgumentException("Unknown command: " + name);
}
return command;
})
.collect(Collectors.toList());
if (commands.isEmpty()) {
printUsage();
throw new IllegalArgumentException("Missing command");
}
return commands;
}
private void printUsage() {
out.println("Arguments: <command1> [<command2> ...]");
out.println("Available commands: " + COMMANDS.keySet());
}
public static void main(final String[] args) {
try {
new App(args).run();
} catch (ExpectedException e) {
e.printStackTrace(System.out);
} catch (Exception e) {
System.err.print("\nUnexpected application error: ");
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -1,57 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Object representation of an asset. Note that the private member variable names don't follow the normal Java naming
* convention as they map to the JSON format expected by the smart contract.
*/
public final class Asset {
private final String ID; // checkstyle:ignore-line:MemberName
private String Color; // checkstyle:ignore-line:MemberName
private int Size; // checkstyle:ignore-line:MemberName
private String Owner; // checkstyle:ignore-line:MemberName
private int AppraisedValue; // checkstyle:ignore-line:MemberName
public Asset(final String id) {
this.ID = id;
}
public String getId() {
return ID;
}
public String getColor() {
return Color;
}
public void setColor(final String color) {
this.Color = color;
}
public int getSize() {
return Size;
}
public void setSize(final int size) {
this.Size = size;
}
public String getOwner() {
return Owner;
}
public void setOwner(final String owner) {
this.Owner = owner;
}
public int getAppraisedValue() {
return AppraisedValue;
}
public void setAppraisedValue(final int appraisedValue) {
this.AppraisedValue = appraisedValue;
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.gson.Gson;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.SubmitException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public final class AssetTransferBasic {
private static final Gson GSON = new Gson();
private final Contract contract;
public AssetTransferBasic(final Contract contract) {
this.contract = contract;
}
public void createAsset(final Asset asset) throws EndorseException, CommitException, SubmitException, CommitStatusException {
contract.submitTransaction(
"CreateAsset",
asset.getId(),
asset.getColor(),
Integer.toString(asset.getSize()),
asset.getOwner(),
Integer.toString(asset.getAppraisedValue())
);
}
public String transferAsset(final String id, final String newOwner) throws EndorseException, CommitException, SubmitException, CommitStatusException {
var resultBytes = contract.submitTransaction("TransferAsset", id, newOwner);
return new String(resultBytes, StandardCharsets.UTF_8);
}
public void deleteAsset(final String id) throws EndorseException, CommitException, SubmitException, CommitStatusException {
contract.submitTransaction("DeleteAsset", id);
}
public List<Asset> getAllAssets() throws EndorseException, CommitException, SubmitException, CommitStatusException {
var resultBytes = contract.submitTransaction("GetAllAssets");
var resultJson = new String(resultBytes, StandardCharsets.UTF_8);
var assets = GSON.fromJson(resultJson, Asset[].class);
return assets != null ? List.of(assets) : List.of();
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.client.Checkpointer;
import parser.Block;
import parser.Transaction;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public final class BlockProcessor {
private final Block block;
private final Checkpointer checkpointer;
private final Store store;
public BlockProcessor(final Block block, final Checkpointer checkpointer, final Store store) {
this.block = block;
this.checkpointer = checkpointer;
this.store = store;
}
public void process() {
var blockNumber = block.getNumber();
System.out.println("\nReceived block " + Long.toUnsignedString(blockNumber));
try {
var validTransactions = getNewTransactions().stream()
.filter(Transaction::isValid)
.collect(Collectors.toList());
for (var transaction : validTransactions) {
new TransactionProcessor(transaction, blockNumber, store).process();
var transactionId = transaction.getChannelHeader().getTxId();
checkpointer.checkpointTransaction(blockNumber, transactionId);
}
checkpointer.checkpointBlock(blockNumber);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private List<Transaction> getNewTransactions() throws InvalidProtocolBufferException {
var transactions = block.getTransactions();
var lastTransactionId = checkpointer.getTransactionId();
if (lastTransactionId.isEmpty()) {
// No previously processed transactions within this block so all are new
return transactions;
}
var transactionIds = new ArrayList<>();
for (var transaction : transactions) {
transactionIds.add(transaction.getChannelHeader().getTxId());
}
// Ignore transactions up to the last processed transaction ID
var lastProcessedIndex = transactionIds.indexOf(lastTransactionId.get());
if (lastProcessedIndex < 0) {
throw new IllegalArgumentException("Checkpoint transaction ID " + lastTransactionId + " not found in block "
+ Long.toUnsignedString(block.getNumber()) + " containing transactions: " + transactionIds);
}
return transactions.subList(lastProcessedIndex + 1, transactions.size());
}
}

View file

@ -1,11 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import io.grpc.Channel;
public interface Command {
void run(Channel grpcChannel) throws Exception;
}

View file

@ -1,115 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import io.grpc.Channel;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
import org.hyperledger.fabric.client.identity.Signers;
import org.hyperledger.fabric.client.identity.X509Identity;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;
public final class Connections {
public static final String CHANNEL_NAME = Utils.getEnvOrDefault("CHANNEL_NAME", "mychannel");
public static final String CHAINCODE_NAME = Utils.getEnvOrDefault("CHAINCODE_NAME", "basic");
private static final String PEER_NAME = "peer0.org1.example.com";
private static final String MSP_ID = Utils.getEnvOrDefault("MSP_ID", "Org1MSP");
// Path to crypto materials.
private static final Path CRYPTO_PATH = Utils.getEnvOrDefault(
"CRYPTO_PATH",
Paths::get,
Paths.get("..", "..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com")
);
// Path to user private key directory.
private static final Path KEY_DIR_PATH = Utils.getEnvOrDefault(
"KEY_DIRECTORY_PATH",
Paths::get,
CRYPTO_PATH.resolve(Paths.get("users", "User1@org1.example.com", "msp", "keystore"))
);
// Path to user certificate.
private static final Path CERT_PATH = Utils.getEnvOrDefault(
"CERT_PATH",
Paths::get,
CRYPTO_PATH.resolve(Paths.get("users", "User1@org1.example.com", "msp", "signcerts", "cert.pem"))
);
// Path to peer tls certificate.
private static final Path TLS_CERT_PATH = Utils.getEnvOrDefault(
"TLS_CERT_PATH",
Paths::get,
CRYPTO_PATH.resolve(Paths.get("peers", PEER_NAME, "tls", "ca.crt"))
);
// Gateway peer end point.
private static final String PEER_ENDPOINT = Utils.getEnvOrDefault("PEER_ENDPOINT", "localhost:7051");
// Gateway peer SSL host name override.
private static final String PEER_HOST_ALIAS = Utils.getEnvOrDefault("PEER_HOST_ALIAS", PEER_NAME);
private static final long EVALUATE_TIMEOUT_SECONDS = 5;
private static final long ENDORSE_TIMEOUT_SECONDS = 15;
private static final long SUBMIT_TIMEOUT_SECONDS = 5;
private static final long COMMIT_STATUS_TIMEOUT_SECONDS = 60;
private Connections() {
// Private constructor to prevent instantiation
}
public static ManagedChannel newGrpcConnection() throws IOException {
var credentials = TlsChannelCredentials.newBuilder()
.trustManager(TLS_CERT_PATH.toFile())
.build();
return Grpc.newChannelBuilder(PEER_ENDPOINT, credentials)
.overrideAuthority(PEER_HOST_ALIAS)
.build();
}
public static Gateway.Builder newGatewayBuilder(final Channel grpcChannel) throws CertificateException, IOException, InvalidKeyException {
return Gateway.newInstance()
.identity(newIdentity())
.signer(newSigner())
.connection(grpcChannel)
.evaluateOptions(options -> options.withDeadlineAfter(EVALUATE_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(ENDORSE_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.submitOptions(options -> options.withDeadlineAfter(SUBMIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.commitStatusOptions(options -> options.withDeadlineAfter(COMMIT_STATUS_TIMEOUT_SECONDS, TimeUnit.SECONDS));
}
private static Identity newIdentity() throws IOException, CertificateException {
var certReader = Files.newBufferedReader(CERT_PATH);
var certificate = Identities.readX509Certificate(certReader);
return new X509Identity(MSP_ID, certificate);
}
private static Signer newSigner() throws IOException, InvalidKeyException {
var keyReader = Files.newBufferedReader(getPrivateKeyPath());
var privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
private static Path getPrivateKeyPath() throws IOException {
try (var keyFiles = Files.list(KEY_DIR_PATH)) {
return keyFiles.findFirst().orElseThrow();
}
}
}

View file

@ -1,11 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
public class ExpectedException extends RuntimeException {
public ExpectedException(final String message) {
super(message);
}
}

View file

@ -1,37 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.grpc.Channel;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.SubmitException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
public final class GetAllAssets implements Command {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@Override
public void run(final Channel grpcChannel)
throws CertificateException, IOException, InvalidKeyException, EndorseException, CommitException, SubmitException, CommitStatusException {
try (Gateway gateway = Connections.newGatewayBuilder(grpcChannel).connect()) {
var network = gateway.getNetwork(Connections.CHANNEL_NAME);
var contract = network.getContract(Connections.CHAINCODE_NAME);
var smartContract = new AssetTransferBasic(contract);
var assets = smartContract.getAllAssets();
var assetsJson = GSON.toJson(assets);
System.out.println(assetsJson);
}
}
}

View file

@ -1,80 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.gson.Gson;
import io.grpc.Channel;
import org.hyperledger.fabric.client.FileCheckpointer;
import parser.BlockParser;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
import java.util.List;
public final class Listen implements Command {
private static final Path CHECKPOINT_FILE = Paths.get(Utils.getEnvOrDefault("CHECKPOINT_FILE", "checkpoint.json"));
private static final Path STORE_FILE = Paths.get(Utils.getEnvOrDefault("STORE_FILE", "store.log"));
private static final int SIMULATED_FAILURE_COUNT = Utils.getEnvOrDefault("SIMULATED_FAILURE_COUNT", Integer::parseUnsignedInt, 0);
private static final long START_BLOCK = 0L;
private static final Gson GSON = new Gson();
private int transactionCount = 0; // Used only to simulate failures
@Override
public void run(final Channel grpcChannel)
throws CertificateException, IOException, InvalidKeyException {
try (var gateway = Connections.newGatewayBuilder(grpcChannel).connect();
var checkpointer = new FileCheckpointer(CHECKPOINT_FILE)) {
var network = gateway.getNetwork(Connections.CHANNEL_NAME);
System.out.println("Starting event listening from block " + Long.toUnsignedString(checkpointer.getBlockNumber().orElse(START_BLOCK)));
System.out.println(checkpointer.getTransactionId()
.map(transactionId -> "Last processed transaction ID within block: " + transactionId)
.orElse("No last processed transaction ID"));
if (SIMULATED_FAILURE_COUNT > 0) {
System.out.println("Simulating a write failure every " + SIMULATED_FAILURE_COUNT + " transactions");
}
try (var blocks = network.newBlockEventsRequest()
.startBlock(START_BLOCK) // Used only if there is no checkpoint block number
.checkpoint(checkpointer)
.build()
.getEvents()) {
blocks.forEachRemaining(blockProto -> {
var block = BlockParser.parseBlock(blockProto);
var processor = new BlockProcessor(block, checkpointer, this::applyWritesToOffChainStore);
processor.process();
});
}
}
}
private void applyWritesToOffChainStore(final long blockNumber, final String transactionId, final List<Write> writes) throws IOException {
simulateFailureIfRequired();
try (var writer = new StringWriter()) {
for (var write : writes) {
GSON.toJson(write, writer);
writer.append('\n');
}
Files.writeString(STORE_FILE, writer.toString(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
}
private void simulateFailureIfRequired() {
if (SIMULATED_FAILURE_COUNT > 0 && transactionCount++ >= SIMULATED_FAILURE_COUNT) {
transactionCount = 0;
throw new ExpectedException("Simulated write failure");
}
}
}

View file

@ -1,13 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.IOException;
import java.util.List;
@FunctionalInterface
public interface Store {
void store(long blockNumber, String transactionId, List<Write> writes) throws IOException;
}

View file

@ -1,27 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import io.grpc.Channel;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
public final class Transact implements Command {
@Override
public void run(final Channel grpcChannel)
throws CertificateException, IOException, InvalidKeyException {
try (var gateway = Connections.newGatewayBuilder(grpcChannel).connect()) {
var network = gateway.getNetwork(Connections.CHANNEL_NAME);
var contract = network.getContract(Connections.CHAINCODE_NAME);
var smartContract = new AssetTransferBasic(contract);
var app = new TransactApp(smartContract);
app.run();
}
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.SubmitException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
public final class TransactApp {
private static final List<String> COLORS = List.of("red", "green", "blue");
private static final List<String> OWNERS = List.of("alice", "bob", "charlie");
private static final int MAX_INITIAL_VALUE = 1000;
private static final int MAX_INITIAL_SIZE = 10;
private final AssetTransferBasic smartContract;
private final int batchSize = 10;
public TransactApp(final AssetTransferBasic smartContract) {
this.smartContract = smartContract;
}
public void run() {
var futures = Stream.generate(this::newCompletableFuture)
.limit(batchSize)
.toArray(CompletableFuture[]::new);
var allComplete = CompletableFuture.allOf(futures);
allComplete.join();
}
private CompletableFuture<Void> newCompletableFuture() {
return CompletableFuture.runAsync(() -> {
try {
transact();
} catch (Exception e) {
throw new CompletionException(e);
}
});
}
private void transact() throws EndorseException, CommitException, SubmitException, CommitStatusException {
var asset = newAsset();
smartContract.createAsset(asset);
System.out.println("Created new asset " + asset.getId());
// Transfer randomly 1 in 2 assets to a new owner.
if (Utils.randomInt(2) == 0) { // checkstyle:ignore-line:MagicNumber
var newOwner = Utils.differentElement(OWNERS, asset.getOwner());
var oldOwner = smartContract.transferAsset(asset.getId(), newOwner);
System.out.println("Transferred asset " + asset.getId() + " from " + oldOwner + " to " + newOwner);
}
// Delete randomly 1 in 4 created assets.
if (Utils.randomInt(4) == 0) { // checkstyle:ignore-line:MagicNumber
smartContract.deleteAsset(asset.getId());
System.out.println("Deleted asset " + asset.getId());
}
}
private Asset newAsset() {
var asset = new Asset(UUID.randomUUID().toString());
asset.setColor(Utils.randomElement(COLORS));
asset.setSize(Utils.randomInt(MAX_INITIAL_SIZE) + 1);
asset.setOwner(Utils.randomElement(OWNERS));
asset.setAppraisedValue(Utils.randomInt(MAX_INITIAL_VALUE) + 1);
return asset;
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.protobuf.InvalidProtocolBufferException;
import parser.Transaction;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public final class TransactionProcessor {
// Typically we should ignore read/write sets that apply to system chaincode namespaces.
private static final Set<String> SYSTEM_CHAINCODE_NAMES = Set.of(
"_lifecycle",
"cscc",
"escc",
"lscc",
"qscc",
"vscc"
);
private final long blockNumber;
private final Transaction transaction;
private final Store store;
public TransactionProcessor(final Transaction transaction, final long blockNumber, final Store store) {
this.blockNumber = blockNumber;
this.transaction = transaction;
this.store = store;
}
private static boolean isSystemChaincode(final String chaincodeName) {
return SYSTEM_CHAINCODE_NAMES.contains(chaincodeName);
}
public void process() throws IOException {
var transactionId = transaction.getChannelHeader().getTxId();
var writes = getWrites();
if (writes.isEmpty()) {
System.out.println("Skipping read-only or system transaction " + transactionId);
return;
}
System.out.println("Process transaction " + transactionId);
store.store(blockNumber, transactionId, writes);
}
private List<Write> getWrites() throws InvalidProtocolBufferException {
var channelName = transaction.getChannelHeader().getChannelId();
var writes = new ArrayList<Write>();
for (var readWriteSet : transaction.getNamespaceReadWriteSets()) {
var namespace = readWriteSet.getNamespace();
if (isSystemChaincode(namespace)) {
continue;
}
readWriteSet.getReadWriteSet().getWritesList().stream()
.map(write -> new Write(channelName, namespace, write))
.forEach(writes::add);
}
return writes;
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
public final class Utils {
private static final Random RANDOM = new Random();
public static String getEnvOrDefault(final String name, final String defaultValue) {
return getEnvOrDefault(name, Function.identity(), defaultValue);
}
public static <T> T getEnvOrDefault(final String name, final Function<String, T> map, final T defaultValue) {
var result = System.getenv(name);
return result != null ? map.apply(result) : defaultValue;
}
/**
* Generate a random integer in the range 0 to max - 1.
* @param max Maximum value (exclusive).
* @return A random number.
*/
public static int randomInt(final int max) {
return RANDOM.nextInt(max);
}
/**
* Pick a random element from a list.
* @param values Candidate elements.
* @return A randomly selected value.
* @param <T> Element type.
*/
public static <T> T randomElement(final List<? extends T> values) {
return values.get(randomInt(values.size()));
}
/**
* Pick a random element from an array, excluding the current value.
* @param values Candidate elements.
* @param currentValue Value to avoid.
* @return A random value.
* @param <T> Element type.
*/
public static <T> T differentElement(final List<? extends T> values, final T currentValue) {
var candidateValues = values.stream()
.filter(value -> !currentValue.equals(value))
.collect(Collectors.toList());
return randomElement(candidateValues);
}
private Utils() { }
}

View file

@ -1,68 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KVWrite;
import java.nio.charset.StandardCharsets;
/**
* Description of a ledger write that can be applied to an off-chain data store.
*/
public final class Write {
private final String channelName;
private final String namespace;
private final String key;
private final boolean isDelete;
private final String value; // Store as String for readability when serialized to JSON.
public Write(final String channelName, final String namespace, final KVWrite write) {
this.channelName = channelName;
this.namespace = namespace;
this.key = write.getKey();
this.isDelete = write.getIsDelete();
this.value = write.getValue().toString(StandardCharsets.UTF_8);
}
/**
* Channel whose ledger is being updated.
* @return A channel name.
*/
public String getChannelName() {
return channelName;
}
/**
* Key name within the ledger namespace.
* @return A ledger key.
*/
public String getKey() {
return key;
}
/**
* Whether the key and associated value are being deleted.
* @return {@code true} if the ledger key is being deleted; otherwise {@code false}.
*/
public boolean isDelete() {
return isDelete;
}
/**
* Namespace within the ledger.
* @return A ledger namespace.
*/
public String getNamespace() {
return namespace;
}
/**
* If {@link #isDelete()}` is {@code false}, the value written to the key; otherwise ignored.
* @return A ledger value.
*/
public byte[] getValue() {
return value.getBytes(StandardCharsets.UTF_8);
}
}

View file

@ -1,17 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.List;
public interface Block {
long getNumber();
List<Transaction> getTransactions() throws InvalidProtocolBufferException;
org.hyperledger.fabric.protos.common.Block toProto();
}

View file

@ -1,15 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
public final class BlockParser {
public static Block parseBlock(final org.hyperledger.fabric.protos.common.Block block) {
return new ParsedBlock(block);
}
private BlockParser() { }
}

View file

@ -1,17 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.protos.ledger.rwset.NsReadWriteSet;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KVRWSet;
public interface NamespaceReadWriteSet {
String getNamespace();
KVRWSet getReadWriteSet() throws InvalidProtocolBufferException;
NsReadWriteSet toProto();
}

View file

@ -1,76 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.protos.common.BlockMetadataIndex;
import org.hyperledger.fabric.protos.common.Envelope;
import org.hyperledger.fabric.protos.common.Payload;
import org.hyperledger.fabric.protos.peer.TxValidationCode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
class ParsedBlock implements Block {
private final org.hyperledger.fabric.protos.common.Block block;
private final AtomicReference<List<Transaction>> cachedTransactions = new AtomicReference<>();
ParsedBlock(final org.hyperledger.fabric.protos.common.Block block) {
this.block = block;
}
@Override
public long getNumber() {
return block.getHeader().getNumber();
}
@Override
public List<Transaction> getTransactions() throws InvalidProtocolBufferException {
return Utils.getCachedProto(cachedTransactions, () -> {
var validationCodes = getTransactionValidationCodes();
var payloads = getPayloads();
var transactions = new ArrayList<Transaction>();
for (int i = 0; i < payloads.size(); i++) {
var payload = new ParsedPayload(payloads.get(i), validationCodes.get(i));
if (payload.isEndorserTransaction()) {
transactions.add(new ParsedTransaction(payload));
}
}
return transactions;
});
}
@Override
public org.hyperledger.fabric.protos.common.Block toProto() {
return block;
}
private List<Payload> getPayloads() throws InvalidProtocolBufferException {
var payloads = new ArrayList<Payload>();
for (var envelopeBytes : block.getData().getDataList()) {
var envelope = Envelope.parseFrom(envelopeBytes);
var payload = Payload.parseFrom(envelope.getPayload());
payloads.add(payload);
}
return payloads;
}
private List<TxValidationCode> getTransactionValidationCodes() {
var transactionsFilter = block.getMetadata().getMetadataList().get(BlockMetadataIndex.TRANSACTIONS_FILTER.getNumber());
return StreamSupport.stream(transactionsFilter.spliterator(), false)
.map(Byte::intValue)
.map(TxValidationCode::forNumber)
.collect(Collectors.toList());
}
}

View file

@ -1,52 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.protos.common.ChannelHeader;
import org.hyperledger.fabric.protos.common.HeaderType;
import org.hyperledger.fabric.protos.common.Payload;
import org.hyperledger.fabric.protos.common.SignatureHeader;
import org.hyperledger.fabric.protos.peer.TxValidationCode;
import java.util.concurrent.atomic.AtomicReference;
class ParsedPayload {
private final Payload payload;
private final TxValidationCode statusCode;
private final AtomicReference<ChannelHeader> cachedChannelHeader = new AtomicReference<>();
private final AtomicReference<SignatureHeader> cachedSignatureHeader = new AtomicReference<>();
ParsedPayload(final Payload payload, final TxValidationCode statusCode) {
this.payload = payload;
this.statusCode = statusCode;
}
public ChannelHeader getChannelHeader() throws InvalidProtocolBufferException {
return Utils.getCachedProto(cachedChannelHeader, () -> ChannelHeader.parseFrom(payload.getHeader().getChannelHeader()));
}
public SignatureHeader getSignatureHeader() throws InvalidProtocolBufferException {
return Utils.getCachedProto(cachedSignatureHeader, () -> SignatureHeader.parseFrom(payload.getHeader().getSignatureHeader()));
}
public TxValidationCode getValidationCode() {
return statusCode;
}
public boolean isValid() {
return statusCode == TxValidationCode.VALID;
}
public Payload toProto() {
return payload;
}
public boolean isEndorserTransaction() throws InvalidProtocolBufferException {
return getChannelHeader().getType() == HeaderType.ENDORSER_TRANSACTION_VALUE;
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.protos.ledger.rwset.NsReadWriteSet;
import org.hyperledger.fabric.protos.ledger.rwset.TxReadWriteSet;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KVRWSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
class ParsedReadWriteSet implements NamespaceReadWriteSet {
private final NsReadWriteSet readWriteSet;
private final AtomicReference<KVRWSet> cachedReadWriteSet = new AtomicReference<>();
static List<ParsedReadWriteSet> fromTxReadWriteSet(final TxReadWriteSet readWriteSet) {
var dataModel = readWriteSet.getDataModel();
if (dataModel != TxReadWriteSet.DataModel.KV) {
throw new IllegalArgumentException("Unexpected read/write set data model: " + dataModel.name());
}
return readWriteSet.getNsRwsetList().stream()
.map(ParsedReadWriteSet::new)
.collect(Collectors.toList());
}
ParsedReadWriteSet(final NsReadWriteSet readWriteSet) {
this.readWriteSet = readWriteSet;
}
@Override
public String getNamespace() {
return readWriteSet.getNamespace();
}
@Override
public KVRWSet getReadWriteSet() throws InvalidProtocolBufferException {
return Utils.getCachedProto(cachedReadWriteSet, () -> KVRWSet.parseFrom(readWriteSet.getRwset()));
}
@Override
public NsReadWriteSet toProto() {
return readWriteSet;
}
}

View file

@ -1,89 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.protos.common.ChannelHeader;
import org.hyperledger.fabric.protos.common.Payload;
import org.hyperledger.fabric.protos.msp.SerializedIdentity;
import org.hyperledger.fabric.protos.peer.TxValidationCode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
final class ParsedTransaction implements Transaction {
private final ParsedPayload payload;
private final AtomicReference<List<NamespaceReadWriteSet>> cachedNamespaceReadWriteSets = new AtomicReference<>();
ParsedTransaction(final ParsedPayload payload) {
this.payload = payload;
}
@Override
public ChannelHeader getChannelHeader() throws InvalidProtocolBufferException {
return payload.getChannelHeader();
}
@Override
public Identity getCreator() throws InvalidProtocolBufferException {
var creator = SerializedIdentity.parseFrom(payload.getSignatureHeader().getCreator());
return new Identity() {
@Override
public String getMspId() {
return creator.getMspid();
}
@Override
public byte[] getCredentials() {
return creator.getIdBytes().toByteArray();
}
};
}
@Override
public TxValidationCode getValidationCode() {
return payload.getValidationCode();
}
@Override
public boolean isValid() {
return payload.isValid();
}
@Override
public List<NamespaceReadWriteSet> getNamespaceReadWriteSets() throws InvalidProtocolBufferException {
return Utils.getCachedProto(cachedNamespaceReadWriteSets, () -> new ArrayList<>(getReadWriteSets()));
}
@Override
public Payload toProto() {
return payload.toProto();
}
private List<ParsedReadWriteSet> getReadWriteSets() throws InvalidProtocolBufferException {
var results = new ArrayList<ParsedReadWriteSet>();
for (var action : getTransactionActions()) {
results.addAll(action.getReadWriteSets());
}
return results;
}
private List<ParsedTransactionAction> getTransactionActions() throws InvalidProtocolBufferException {
return getTransaction().getActionsList().stream()
.map(ParsedTransactionAction::new)
.collect(Collectors.toList());
}
private org.hyperledger.fabric.protos.peer.Transaction getTransaction() throws InvalidProtocolBufferException {
return org.hyperledger.fabric.protos.peer.Transaction.parseFrom(payload.toProto().getData());
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.protos.ledger.rwset.TxReadWriteSet;
import org.hyperledger.fabric.protos.peer.ChaincodeAction;
import org.hyperledger.fabric.protos.peer.ChaincodeActionPayload;
import org.hyperledger.fabric.protos.peer.ProposalResponsePayload;
import org.hyperledger.fabric.protos.peer.TransactionAction;
import java.util.List;
final class ParsedTransactionAction {
private final TransactionAction transactionAction;
ParsedTransactionAction(final TransactionAction transactionAction) {
this.transactionAction = transactionAction;
}
public List<ParsedReadWriteSet> getReadWriteSets() throws InvalidProtocolBufferException {
return ParsedReadWriteSet.fromTxReadWriteSet(getTxReadWriteSet());
}
private TxReadWriteSet getTxReadWriteSet() throws InvalidProtocolBufferException {
return TxReadWriteSet.parseFrom(getChaincodeAction().getResults());
}
private ChaincodeAction getChaincodeAction() throws InvalidProtocolBufferException {
return ChaincodeAction.parseFrom(getProposalResponsePayload().getExtension());
}
private ProposalResponsePayload getProposalResponsePayload() throws InvalidProtocolBufferException {
return ProposalResponsePayload.parseFrom(getChaincodeActionPayload().getAction().getProposalResponsePayload());
}
private ChaincodeActionPayload getChaincodeActionPayload() throws InvalidProtocolBufferException {
return ChaincodeActionPayload.parseFrom(transactionAction.getPayload());
}
public TransactionAction toProto() {
return transactionAction;
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.protos.common.ChannelHeader;
import org.hyperledger.fabric.protos.common.Payload;
import org.hyperledger.fabric.protos.peer.TxValidationCode;
import java.util.List;
public interface Transaction {
ChannelHeader getChannelHeader() throws InvalidProtocolBufferException;
Identity getCreator() throws InvalidProtocolBufferException;
TxValidationCode getValidationCode();
boolean isValid();
List<NamespaceReadWriteSet> getNamespaceReadWriteSets() throws InvalidProtocolBufferException;
Payload toProto();
}

View file

@ -1,51 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
final class Utils {
public interface ProtoCall<T> extends Callable<T> {
@Override
T call() throws InvalidProtocolBufferException;
}
public static <T> T getCachedProto(final AtomicReference<T> cache, final ProtoCall<T> call) throws InvalidProtocolBufferException {
try {
return cache.updateAndGet(current -> current != null ? current : asSupplier(call).get());
} catch (CompletionException e) {
var cause = e.getCause();
if (cause instanceof InvalidProtocolBufferException) {
throw (InvalidProtocolBufferException) cause;
}
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw e;
}
}
public static <T> Supplier<T> asSupplier(final Callable<T> call) {
return () -> {
try {
return call.call();
} catch (Exception e) {
throw new CompletionException(e);
}
};
}
private Utils() { }
}

View file

@ -1,197 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Only public (API, SPI) classes must have Javadoc -->
<!-- <module name="SuppressionSingleFilter">-->
<!-- <property name="checks" value=".*Javadoc.*"/>-->
<!-- <property name="files" value="src/main/java/org/hyperledger/fabric/client/impl"/>-->
<!-- </module>-->
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See https://checkstyle.org/config_javadoc.html#JavadocPackage -->
<!-- <module name="JavadocPackage"/>-->
<!-- Checks whether files end with a new line. -->
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See https://checkstyle.org/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="FileLength"/>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="ignorePattern" value="^ +\* +"/>
<property name="max" value="160"/>
</module>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/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 https://checkstyle.org/config_header.html -->
<module name="RegexpHeader">
<property name="headerFile" value="${config_loc}/java-copyright-header.txt"/>
<property name="fileExtensions" value="java"/>
</module>
<module name="TreeWalker">
<!-- Checkstyle ignore current line with a comment: // checkstyle:ignore-line:RuleName1|RuleName2 -->
<module name="SuppressWithNearbyCommentFilter">
<property name="commentFormat" value="checkstyle:ignore-line:(\w+(\|\w+)*)"/>
<property name="checkFormat" value="$1"/>
</module>
<!-- Checkstyle ignore next line with a comment: // checkstyle:ignore-next-line:RuleName1|RuleName2 -->
<module name="SuppressWithNearbyCommentFilter">
<property name="commentFormat" value="checkstyle:ignore-next-line:(\w+(\|\w+)*)"/>
<property name="checkFormat" value="$1"/>
<property name="influenceFormat" value="1"/>
</module>
<!-- Checks for Javadoc comments. -->
<!-- See https://checkstyle.org/config_javadoc.html -->
<module name="InvalidJavadocPosition"/>
<module name="JavadocContentLocation"/>
<module name="JavadocMethod"/>
<module name="JavadocMissingLeadingAsterisk"/>
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
<module name="JavadocParagraph"/>
<module name="JavadocStyle"/>
<module name="JavadocType"/>
<!-- <module name="JavadocVariable">-->
<!-- <property name="scope" value="protected"/>-->
<!-- </module>-->
<module name="JavadocStyle"/>
<!-- <module name="MissingJavadocMethod"/>-->
<!-- <module name="MissingJavadocType"/>-->
<module name="NonEmptyAtclauseDescription"/>
<!-- Checks for Naming Conventions. -->
<!-- See https://checkstyle.org/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See https://checkstyle.org/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 https://checkstyle.org/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<!-- <module name="EmptyLineSeparator">-->
<!-- <property name="allowNoEmptyLineBetweenFields" value="true"/>-->
<!-- <property name="allowMultipleEmptyLines" value="false"/>-->
<!-- <property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/>-->
<!-- </module>-->
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="SingleSpaceSeparator"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See https://checkstyle.org/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See https://checkstyle.org/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See https://checkstyle.org/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MagicNumber"/>
<module name="MissingSwitchDefault"/>
<module name="NoFinalizer"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="UnnecessarySemicolonAfterOuterTypeDeclaration"/>
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration"/>
<module name="UnnecessarySemicolonInEnumeration"/>
<module name="UnnecessarySemicolonInTryWithResources"/>
<module name="UnusedLocalVariable"/>
<!-- Checks for class design -->
<!-- See https://checkstyle.org/config_design.html -->
<module name="DesignForExtension"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier"/>
<!-- Checks for metrics -->
<!-- See https://checkstyle.org/config_metrics.html -->
<module name="CyclomaticComplexity">
<property name="max" value="10"/>
</module>
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="Indentation"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>

View file

@ -1,5 +0,0 @@
^/\*$
^ \* Copyright IBM Corp\. All Rights Reserved\.$
^ \*$
^ \* SPDX-License-Identifier: Apache-2\.0$
^ \*/$

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,8 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
rootProject.name = 'off-chain-data'
include('app')

View file

@ -1,17 +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
# Files generated by the application at runtime
checkpoint.json
store.log

View file

@ -1 +0,0 @@
engine-strict=true

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": "off-chain-data",
"version": "1.0.0",
"description": "Off-chain data store application",
"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"
},
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.10",
"@hyperledger/fabric-gateway": "^1.5",
"@hyperledger/fabric-protos": "^0.2.1"
},
"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,57 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { newGrpcConnection } from './connect';
import { ExpectedError } from './expectedError';
import { main as getAllAssets } from './getAllAssets';
import { main as listen } from './listen';
import { main as transact } from './transact';
const allCommands: Record<string, (client: grpc.Client) => Promise<void>> = {
getAllAssets,
listen,
transact,
};
async function main(): Promise<void> {
const commands = process.argv.slice(2).map(name => {
const command = allCommands[name];
if (!command) {
printUsage();
throw new Error(`Unknown command: ${name}`);
}
return command;
});
if (commands.length === 0) {
printUsage();
throw new Error('Missing command');
}
const client = await newGrpcConnection();
try {
for (const command of commands) {
await command(client);
}
} finally {
client.close();
}
}
function printUsage(): void {
console.log('Arguments: <command1> [<command2> ...]');
console.log('Available commands:', Object.keys(allCommands).join(', '));
}
main().catch((error: unknown) => {
if (error instanceof ExpectedError) {
console.log(error);
} else {
console.error('\nUnexpected application error:', error);
process.exitCode = 1;
}
});

View file

@ -1,229 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Identity } from '@hyperledger/fabric-gateway';
import { common, ledger, msp, peer } from '@hyperledger/fabric-protos';
import { assertDefined, cache } from './utils';
export interface Block {
getNumber(): bigint;
getTransactions(): Transaction[];
toProto(): common.Block;
}
export interface Transaction {
getChannelHeader(): common.ChannelHeader;
getCreator(): Identity;
getValidationCode(): number;
isValid(): boolean;
getNamespaceReadWriteSets(): NamespaceReadWriteSet[];
toProto(): common.Payload;
}
export interface NamespaceReadWriteSet {
getNamespace(): string;
getReadWriteSet(): ledger.rwset.kvrwset.KVRWSet;
toProto(): ledger.rwset.NsReadWriteSet;
}
export function parseBlock(block: common.Block): Block {
const validationCodes = getTransactionValidationCodes(block);
const header = assertDefined(block.getHeader(), 'Missing block header');
return {
getNumber: () => BigInt(header.getNumber()),
getTransactions: cache(() =>
getPayloads(block)
.map((payload, i) =>
parsePayload(
payload,
assertDefined(
validationCodes[i],
`Missing validation code index {String(i)}`
)
)
)
.filter((payload) => payload.isEndorserTransaction())
.map(newTransaction)
),
toProto: () => block,
};
}
interface Payload {
getChannelHeader(): common.ChannelHeader;
getEndorserTransaction(): EndorserTransaction;
getSignatureHeader(): common.SignatureHeader;
getTransactionValidationCode(): number;
isEndorserTransaction(): boolean;
isValid(): boolean;
toProto(): common.Payload;
}
interface EndorserTransaction {
getReadWriteSets(): ReadWriteSet[];
toProto(): peer.Transaction;
}
interface ReadWriteSet {
getNamespaceReadWriteSets(): NamespaceReadWriteSet[];
toProto(): ledger.rwset.TxReadWriteSet;
}
function parsePayload(payload: common.Payload, statusCode: number): Payload {
const cachedChannelHeader = cache(() => getChannelHeader(payload));
const isEndorserTransaction = (): boolean =>
cachedChannelHeader().getType() ===
common.HeaderType.ENDORSER_TRANSACTION;
return {
getChannelHeader: cachedChannelHeader,
getEndorserTransaction: () => {
if (!isEndorserTransaction()) {
throw new Error(
`Unexpected payload type: ${String(
cachedChannelHeader().getType()
)}`
);
}
const transaction = peer.Transaction.deserializeBinary(
payload.getData_asU8()
);
return parseEndorserTransaction(transaction);
},
getSignatureHeader: cache(() => getSignatureHeader(payload)),
getTransactionValidationCode: () => statusCode,
isEndorserTransaction,
isValid: () => statusCode === peer.TxValidationCode.VALID,
toProto: () => payload,
};
}
function parseEndorserTransaction(
transaction: peer.Transaction
): EndorserTransaction {
return {
getReadWriteSets: cache(() =>
getChaincodeActionPayloads(transaction)
.map((payload) =>
assertDefined(
payload.getAction(),
'Missing chaincode endorsed action'
)
)
.map((endorsedAction) =>
endorsedAction.getProposalResponsePayload_asU8()
)
.map((bytes) =>
peer.ProposalResponsePayload.deserializeBinary(bytes)
)
.map((responsePayload) =>
peer.ChaincodeAction.deserializeBinary(
responsePayload.getExtension_asU8()
)
)
.map((chaincodeAction) => chaincodeAction.getResults_asU8())
.map((bytes) =>
ledger.rwset.TxReadWriteSet.deserializeBinary(bytes)
)
.map(parseReadWriteSet)
),
toProto: () => transaction,
};
}
function newTransaction(payload: Payload): Transaction {
const transaction = payload.getEndorserTransaction();
return {
getChannelHeader: () => payload.getChannelHeader(),
getCreator: () => {
const creatorBytes = payload.getSignatureHeader().getCreator_asU8();
const creator =
msp.SerializedIdentity.deserializeBinary(creatorBytes);
return {
mspId: creator.getMspid(),
credentials: creator.getIdBytes_asU8(),
};
},
getNamespaceReadWriteSets: () =>
transaction
.getReadWriteSets()
.flatMap((readWriteSet) =>
readWriteSet.getNamespaceReadWriteSets()
),
getValidationCode: () => payload.getTransactionValidationCode(),
isValid: () => payload.isValid(),
toProto: () => payload.toProto(),
};
}
function parseReadWriteSet(
readWriteSet: ledger.rwset.TxReadWriteSet
): ReadWriteSet {
return {
getNamespaceReadWriteSets: () =>
readWriteSet.getNsRwsetList().map(parseNamespaceReadWriteSet),
toProto: () => readWriteSet,
};
}
function parseNamespaceReadWriteSet(
nsReadWriteSet: ledger.rwset.NsReadWriteSet
): NamespaceReadWriteSet {
return {
getNamespace: () => nsReadWriteSet.getNamespace(),
getReadWriteSet: cache(() =>
ledger.rwset.kvrwset.KVRWSet.deserializeBinary(
nsReadWriteSet.getRwset_asU8()
)
),
toProto: () => nsReadWriteSet,
};
}
function getTransactionValidationCodes(block: common.Block): Uint8Array {
const metadata = assertDefined(
block.getMetadata(),
'Missing block metadata'
);
return assertDefined(
metadata.getMetadataList_asU8()[
common.BlockMetadataIndex.TRANSACTIONS_FILTER
],
'Missing transaction validation code'
);
}
function getPayloads(block: common.Block): common.Payload[] {
return (block.getData()?.getDataList_asU8() ?? [])
.map((bytes) => common.Envelope.deserializeBinary(bytes))
.map((envelope) => envelope.getPayload_asU8())
.map((bytes) => common.Payload.deserializeBinary(bytes));
}
function getChannelHeader(payload: common.Payload): common.ChannelHeader {
const header = assertDefined(payload.getHeader(), 'Missing payload header');
return common.ChannelHeader.deserializeBinary(
header.getChannelHeader_asU8()
);
}
function getSignatureHeader(payload: common.Payload): common.SignatureHeader {
const header = assertDefined(payload.getHeader(), 'Missing payload header');
return common.SignatureHeader.deserializeBinary(
header.getSignatureHeader_asU8()
);
}
function getChaincodeActionPayloads(
transaction: peer.Transaction
): peer.ChaincodeActionPayload[] {
return transaction
.getActionsList()
.map((transactionAction) => transactionAction.getPayload_asU8())
.map((bytes) => peer.ChaincodeActionPayload.deserializeBinary(bytes));
}

View file

@ -1,81 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { ConnectOptions, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
export const channelName = process.env.CHANNEL_NAME ?? 'mychannel';
export const chaincodeName = process.env.CHAINCODE_NAME ?? 'basic';
const peerName = 'peer0.org1.example.com';
const mspId = process.env.MSP_ID ?? 'Org1MSP';
// Path to crypto materials.
const cryptoPath = path.resolve(process.env.CRYPTO_PATH ?? path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'));
// Path to user private key directory.
const keyDirectoryPath = path.resolve(process.env.KEY_DIRECTORY_PATH ?? path.resolve(__dirname, cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'));
// Path to user certificate.
const certPath = path.resolve(process.env.CERT_PATH ?? path.resolve(__dirname, cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem'));
// Path to peer tls certificate.
const tlsCertPath = path.resolve(process.env.TLS_CERT_PATH ?? path.resolve(__dirname, cryptoPath, 'peers', peerName, 'tls', 'ca.crt'));
// Gateway peer endpoint.
const peerEndpoint = process.env.PEER_ENDPOINT ?? 'localhost:7051';
// Gateway peer SSL host name override.
const peerHostAlias = process.env.PEER_HOST_ALIAS ?? peerName;
export async function newGrpcConnection(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': peerHostAlias,
});
}
export async function newConnectOptions(client: grpc.Client): Promise<ConnectOptions> {
return {
client,
identity: await newIdentity(),
signer: await newSigner(),
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
endorseOptions: () => {
return { deadline: Date.now() + 15000 }; // 15 seconds
},
submitOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
commitStatusOptions: () => {
return { deadline: Date.now() + 60000 }; // 1 minute
},
};
}
async function newIdentity(): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
async function newSigner(): Promise<Signer> {
const keyFiles = await fs.readdir(keyDirectoryPath);
const keyFile = keyFiles[0];
if (!keyFile) {
throw new Error(`No private key files found in directory ${keyDirectoryPath}`);
}
const keyPath = path.resolve(keyDirectoryPath, keyFile);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}

View file

@ -1,54 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Contract } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
const utf8Decoder = new TextDecoder();
export interface Asset {
ID: string;
Color: string;
Size: number;
Owner: string;
AppraisedValue: number;
}
export class AssetTransferBasic {
readonly #contract: Contract;
constructor(contract: Contract) {
this.#contract = contract;
}
async createAsset(asset: Asset): Promise<void> {
await this.#contract.submit('CreateAsset', {
arguments: [asset.ID, asset.Color, String(asset.Size), asset.Owner, String(asset.AppraisedValue)],
});
}
async transferAsset(id: string, newOwner: string): Promise<string> {
const result = await this.#contract.submit('TransferAsset', {
arguments: [id, newOwner],
});
return utf8Decoder.decode(result);
}
async deleteAsset(id: string): Promise<void> {
await this.#contract.submit('DeleteAsset', {
arguments: [id],
});
}
async getAllAssets(): Promise<Asset[]> {
const result = await this.#contract.evaluate('GetAllAssets');
if (result.length === 0) {
return [];
}
return JSON.parse(utf8Decoder.decode(result)) as Asset[];
}
}

View file

@ -1,12 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
export class ExpectedError extends Error {
constructor(message?: string) {
super(message);
this.name = ExpectedError.name;
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Client } from '@grpc/grpc-js';
import { connect } from '@hyperledger/fabric-gateway';
import { chaincodeName, channelName, newConnectOptions } from './connect';
import { AssetTransferBasic } from './contract';
export async function main(client: Client): Promise<void> {
const connectOptions = await newConnectOptions(client);
const gateway = connect(connectOptions);
try {
const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
const smartContract = new AssetTransferBasic(contract);
const assets = await smartContract.getAllAssets();
const assetsJson = JSON.stringify(assets, undefined, 2);
// Write line-by-line to avoid truncation
assetsJson.split('\n').forEach((line) => {
console.log(line);
});
} finally {
gateway.close();
}
}

View file

@ -1,254 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Client } from '@grpc/grpc-js';
import { Checkpointer, checkpointers, connect } from '@hyperledger/fabric-gateway';
import { promises as fs } from 'fs';
import * as path from 'path';
import { TextDecoder } from 'util';
import { Block, parseBlock, Transaction } from './blockParser';
import { channelName, newConnectOptions } from './connect';
import { ExpectedError } from './expectedError';
const checkpointFile = path.resolve(process.env.CHECKPOINT_FILE ?? 'checkpoint.json');
const storeFile = path.resolve(process.env.STORE_FILE ?? 'store.log');
const simulatedFailureCount = getSimulatedFailureCount();
const startBlock = BigInt(0);
const utf8Decoder = new TextDecoder();
// Typically we should ignore read/write sets that apply to system chaincode namespaces.
const systemChaincodeNames = [
'_lifecycle',
'cscc',
'escc',
'lscc',
'qscc',
'vscc',
];
let transactionCount = 0; // Used only to simulate failures
/**
* Apply writes for a given transaction to off-chain data store, ideally in a single operation for fault tolerance.
* @param data Transaction data.
*/
type Store = (data: LedgerUpdate) => Promise<void>;
/**
* Ledger update made by a specific transaction.
*/
interface LedgerUpdate {
blockNumber: bigint;
transactionId: string;
writes: Write[];
}
/**
* Description of a ledger write that can be applied to an off-chain data store.
*/
interface Write {
/** Channel whose ledger is being updated. */
channelName: string;
/** Namespace within the ledger. */
namespace: string;
/** Key name within the ledger namespace. */
key: string;
/** Whether the key and associated value are being deleted. */
isDelete: boolean;
/** If `isDelete` is false, the value written to the key; otherwise ignored. */
value: Uint8Array;
}
/**
* Apply writes for a given transaction to off-chain data store, ideally in a single operation for fault tolerance.
* This implementation just writes to a file.
*/
const applyWritesToOffChainStore: Store = async (data) => {
simulateFailureIfRequired();
const writes = data.writes
.map(write => Object.assign({}, write, {
value: utf8Decoder.decode(write.value), // Convert bytes to text, purely for readability in output
}))
.map(write => JSON.stringify(write));
await fs.appendFile(storeFile, writes.join('\n') + '\n');
};
export async function main(client: Client): Promise<void> {
const connectOptions = await newConnectOptions(client);
const gateway = connect(connectOptions);
try {
const network = gateway.getNetwork(channelName);
const checkpointer = await checkpointers.file(checkpointFile);
console.log('Starting event listening from block', checkpointer.getBlockNumber() ?? startBlock);
console.log('Last processed transaction ID within block:', checkpointer.getTransactionId());
if (simulatedFailureCount > 0) {
console.log('Simulating a write failure every', simulatedFailureCount, 'transactions');
}
const blocks = await network.getBlockEvents({
checkpoint: checkpointer,
startBlock, // Used only if there is no checkpoint block number
});
try {
for await (const blockProto of blocks) {
const blockProcessor = new BlockProcessor({
block: parseBlock(blockProto),
checkpointer,
store: applyWritesToOffChainStore,
});
await blockProcessor.process();
}
} finally {
blocks.close();
}
} finally {
gateway.close();
}
}
interface BlockProcessorOptions {
block: Block;
checkpointer: Checkpointer;
store: Store;
}
class BlockProcessor {
readonly #block: Block;
readonly #checkpointer: Checkpointer;
readonly #store: Store;
constructor(options: Readonly<BlockProcessorOptions>) {
this.#block = options.block;
this.#checkpointer = options.checkpointer;
this.#store = options.store;
}
async process(): Promise<void> {
const blockNumber = this.#block.getNumber();
console.log(`\nReceived block ${String(blockNumber)}`);
const validTransactions = this.#getNewTransactions()
.filter(transaction => transaction.isValid());
for (const transaction of validTransactions) {
const transactionProcessor = new TransactionProcessor({
blockNumber,
store: this.#store,
transaction,
});
await transactionProcessor.process();
const transactionId = transaction.getChannelHeader().getTxId();
await this.#checkpointer.checkpointTransaction(blockNumber, transactionId);
}
await this.#checkpointer.checkpointBlock(this.#block.getNumber());
}
#getNewTransactions(): Transaction[] {
const transactions = this.#block.getTransactions();
const lastTransactionId = this.#checkpointer.getTransactionId();
if (!lastTransactionId) {
// No previously processed transactions within this block so all are new
return transactions;
}
// Ignore transactions up to the last processed transaction ID
const blockTransactionIds = transactions.map(transaction => transaction.getChannelHeader().getTxId());
const lastProcessedIndex = blockTransactionIds.indexOf(lastTransactionId);
if (lastProcessedIndex < 0) {
throw new Error(`Checkpoint transaction ID ${lastTransactionId} not found in block ${String(this.#block.getNumber())} containing transactions: ${blockTransactionIds.join(', ')}`);
}
return transactions.slice(lastProcessedIndex + 1);
}
}
interface TransactionProcessorOptions {
blockNumber: bigint;
store: Store;
transaction: Transaction;
}
class TransactionProcessor {
readonly #blockNumber: bigint;
readonly #transaction: Transaction;
readonly #store: Store;
constructor(options: Readonly<TransactionProcessorOptions>) {
this.#blockNumber = options.blockNumber;
this.#transaction = options.transaction;
this.#store = options.store;
}
async process(): Promise<void> {
const channelHeader = this.#transaction.getChannelHeader();
const transactionId = channelHeader.getTxId();
const writes = this.#getWrites();
if (writes.length === 0) {
console.log(`Skipping read-only or system transaction ${transactionId}`);
return;
}
console.log(`Process transaction ${transactionId}`);
await this.#store({
blockNumber: this.#blockNumber,
transactionId,
writes,
});
}
#getWrites(): Write[] {
const channelName = this.#transaction.getChannelHeader().getChannelId();
return this.#transaction.getNamespaceReadWriteSets()
.filter(readWriteSet => !isSystemChaincode(readWriteSet.getNamespace()))
.flatMap(readWriteSet => {
const namespace = readWriteSet.getNamespace();
return readWriteSet.getReadWriteSet().getWritesList().map(write => {
return {
channelName,
namespace,
key: write.getKey(),
isDelete: write.getIsDelete(),
value: write.getValue_asU8(),
};
});
});
}
}
function isSystemChaincode(chaincodeName: string): boolean {
return systemChaincodeNames.includes(chaincodeName);
}
function getSimulatedFailureCount(): number {
const value = process.env.SIMULATED_FAILURE_COUNT ?? '0';
const count = Math.floor(Number(value));
if (isNaN(count) || count < 0) {
throw new Error(`Invalid SIMULATED_FAILURE_COUNT value: ${String(value)}`);
}
return count;
}
function simulateFailureIfRequired(): void {
if (simulatedFailureCount > 0 && transactionCount++ >= simulatedFailureCount) {
transactionCount = 0;
throw new ExpectedError('Simulated write failure');
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Client } from '@grpc/grpc-js';
import { connect } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { chaincodeName, channelName, newConnectOptions } from './connect';
import { Asset, AssetTransferBasic } from './contract';
import { allFulfilled, differentElement, randomElement, randomInt } from './utils';
export async function main(client: Client): Promise<void> {
const connectOptions = await newConnectOptions(client);
const gateway = connect(connectOptions);
try {
const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
const smartContract = new AssetTransferBasic(contract);
const app = new TransactApp(smartContract);
await app.run();
} finally {
gateway.close();
}
}
const colors = ['red', 'green', 'blue'];
const owners = ['alice', 'bob', 'charlie'];
const maxInitialValue = 1000;
const maxInitialSize = 10;
class TransactApp {
readonly #smartContract: AssetTransferBasic;
#batchSize = 10;
constructor(smartContract: AssetTransferBasic) {
this.#smartContract = smartContract;
}
async run(): Promise<void> {
const promises = Array.from({ length: this.#batchSize }, () => this.#transact());
await allFulfilled(promises);
}
async #transact(): Promise<void> {
const asset = this.#newAsset();
await this.#smartContract.createAsset(asset);
console.log(`Created asset ${asset.ID}`);
// Transfer randomly 1 in 2 assets to a new owner.
if (randomInt(2) === 0) {
const newOwner = differentElement(owners, asset.Owner);
const oldOwner = await this.#smartContract.transferAsset(asset.ID, newOwner);
console.log(`Transferred asset ${asset.ID} from ${oldOwner} to ${newOwner}`);
}
// Delete randomly 1 in 4 created assets.
if (randomInt(4) === 0) {
await this.#smartContract.deleteAsset(asset.ID);
console.log(`Deleted asset ${asset.ID}`);
}
}
#newAsset(): Asset {
return {
ID: crypto.randomUUID(),
Color: randomElement(colors),
Size: randomInt(maxInitialSize) + 1,
Owner: randomElement(owners),
AppraisedValue: randomInt(maxInitialValue) + 1,
};
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Pick a random element from an array.
* @param values Candidate elements.
*/
export function randomElement<T>(values: T[]): T {
const result = values[randomInt(values.length)];
return assertDefined(result, `Missing element in {String(values)}`);
}
/**
* Generate a random integer in the range 0 to max - 1.
* @param max Maximum value (exclusive).
*/
export function randomInt(max: number): number {
return Math.floor(Math.random() * max);
}
/**
* Pick a random element from an array, excluding the current value.
* @param values Candidate elements.
* @param currentValue Value to avoid.
*/
export function differentElement<T>(values: T[], currentValue: T): T {
const candidateValues = values.filter(value => value !== currentValue);
return randomElement(candidateValues);
}
/**
* Wait for all promises to complete, then throw an Error only if any of the promises were rejected.
* @param promises Promises to be awaited.
*/
export async function allFulfilled(promises: Promise<unknown>[]): Promise<void> {
const results = await Promise.allSettled(promises);
const failures = results
.map(result => result.status === 'rejected' && result.reason as unknown)
.filter(reason => !!reason);
if (failures.length > 0) {
const failMessages = ' - ' + failures.join('\n - ');
throw new Error(`${String(failures.length)} failures:\n${failMessages}\n`);
}
}
/**
* Return the value if it is defined; otherwise thrown an error.
* @param value A value that might not be defined.
* @param message Error message if the value is not defined.
*/
export function assertDefined<T>(value: T | null | undefined, message: string): T {
if (value == undefined) {
throw new Error(message);
}
return value;
}
/**
* Wrap a function call with a cache. On first call the wrapped function is invoked to obtain a result. Subsequent
* calls return the cached result.
* @param f A function whose result should be cached.
*/
export function cache<T>(f: () => T): () => T {
let value: T | undefined;
return () => {
if (value === undefined) {
value = f();
}
return value;
};
}

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"]
}