Skip to content

Commit 4c97d4c

Browse files
Use Fabric Gateway client API in high-throughput (#1338)
The high-throughput sample used the deprecated Go SDK. This change updates the sample to use the currently supported Fabric Gateway client API and adds some automated testing to ensure the sample works correctly. Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
1 parent 84089e8 commit 4c97d4c

File tree

11 files changed

+279
-556
lines changed

11 files changed

+279
-556
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
name: Test High Throughput
5+
run-name: ${{ github.actor }} is running the High Throughput tests
6+
7+
on:
8+
workflow_dispatch:
9+
push:
10+
branches: ["main", "release-2.5"]
11+
pull_request:
12+
branches: ["main", "release-2.5"]
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
basic:
20+
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Set up test network runtime
26+
uses: ./.github/actions/test-network-setup
27+
28+
- name: Start Fabric
29+
working-directory: high-throughput
30+
run: ./startFabric.sh
31+
32+
- name: Test High Throughput
33+
working-directory: high-throughput/application-go
34+
run: |
35+
go run . manyUpdates testvar1 100 +
36+
go run . prune testvar1
37+
go run . get testvar1
38+
go run . update testvar2 100 +
39+
go run . manyUpdatesTraditional testvar2 100 +
40+
go run . delete testvar2
41+
42+
- name: Stop Fabric
43+
working-directory: high-throughput
44+
run: ./networkDown.sh

high-throughput/README.md

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# High-Throughput Network
88

99
## Purpose
10+
1011
This network is used to understand how to properly design the chaincode data model when handling thousands of transactions per second which all
1112
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
1213
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
@@ -17,6 +18,7 @@ which are aggregated when the value must be retrieved. In this way, no single ro
1718
is considered.
1819

1920
## Use Case
21+
2022
The primary use case for this chaincode data model design is for applications in which a particular asset has an associated amount that is
2123
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
2224
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
@@ -94,18 +96,21 @@ and assumed to be correct and at minimal risk to either company simply due to Am
9496
must be verified before approval and admittance to the chain.
9597

9698
## How
99+
97100
This sample provides the chaincode and scripts required to run a high-throughput application on the Fabric test network.
98101

99102
### Start the network
100103

101104
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.
102105

103106
Change back into the `high-throughput` directory in `fabic-samples`. Start the network and deploy the chaincode by issuing the following command:
107+
104108
```
105109
./startFabric.sh
106110
```
107111

108112
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:
113+
109114
```
110115
Total setup execution time : 81 secs ...
111116
```
@@ -115,60 +120,71 @@ The `high-throughput` chaincode is now ready to receive invocations.
115120
### Invoke the chaincode
116121

117122
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:
123+
118124
```
119125
cd application-go
120126
```
121127

122128
#### Update
123-
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.
124129

125-
Example: `go run app.go update myvar 100 +`
130+
The format for update is: `go run . 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.
131+
132+
Example: `go run . update myvar 100 +`
126133

127134
#### Query
128-
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.
129135

130-
Example: `go run app.go get myvar`
136+
You can query the value of a variable by running `go run . get name` where `name` is the name of the variable to get.
137+
138+
Example: `go run . get myvar`
131139

132140
#### Prune
141+
133142
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.
134143

135-
The format for pruning is: `go run app.go prune name` where `name` is the name of the variable to prune.
144+
The format for pruning is: `go run . prune name` where `name` is the name of the variable to prune.
136145

137-
Example: `go run app.go prune myvar`
146+
Example: `go run . prune myvar`
138147

139148
#### Delete
140-
The format for delete is: `go run app.go delete name` where `name` is the name of the variable to delete.
141149

142-
Example: `go run app.go delete myvar`
150+
The format for delete is: `go run . delete name` where `name` is the name of the variable to delete.
151+
152+
Example: `go run . delete myvar`
143153

144154
### Test the Network
145155

146-
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.
156+
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.
147157

148158
The second function, `manyUpdatesTraditional`, submits 1000 transactions that attempt to update the same key in the world state 1000 times.
149159

150160
Run the following command to create and update `testvar1` a 1000 times:
161+
151162
```
152-
go run app.go manyUpdates testvar1 100 +
163+
go run . manyUpdates testvar1 100 +
153164
```
154165

155166
The application will query the variable after submitting the transaction. The result should be `100000`.
156167

157168
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`:
169+
158170
```
159-
go run app.go update testvar2 100 +
171+
go run . update testvar2 100 +
160172
```
173+
161174
The variable will have a value of 100:
175+
162176
```
163177
2020/10/27 18:01:45 Value of variable testvar2 : 100
164178
```
165179

166180
Now lets try to update `testvar2` 1000 times in parallel:
181+
167182
```
168-
go run app.go manyUpdatesTraditional testvar2 100 +
183+
go run . manyUpdatesTraditional testvar2 100 +
169184
```
170185

171186
When the program ends, you may see that none of the updates succeeded.
187+
172188
```
173189
2020/10/27 18:03:15 Final value of variable testvar2 : 100
174190
```
@@ -177,7 +193,6 @@ The transactions failed because multiple transactions in each block updated the
177193

178194
You can can examine the peer logs to view the messages generated by the rejected blocks:
179195

180-
181196
```
182197
docker logs peer0.org1.example.com
183198
```

high-throughput/application-go/app.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ package main
99
import (
1010
"log"
1111
"os"
12+
"time"
1213

14+
"github.com/hyperledger/fabric-gateway/pkg/client"
15+
"github.com/hyperledger/fabric-gateway/pkg/hash"
1316
f "github.com/hyperledger/fabric-samples/high-throughput/application-go/functions"
1417
)
1518

@@ -32,36 +35,56 @@ func main() {
3235
sign = os.Args[4]
3336
}
3437

38+
clientConnection := newGrpcConnection()
39+
defer clientConnection.Close()
40+
41+
gateway, err := client.Connect(
42+
newIdentity(),
43+
client.WithSign(newSign()),
44+
client.WithHash(hash.SHA256),
45+
client.WithClientConnection(clientConnection),
46+
client.WithEvaluateTimeout(5*time.Second),
47+
client.WithEndorseTimeout(15*time.Second),
48+
client.WithSubmitTimeout(5*time.Second),
49+
client.WithCommitStatusTimeout(1*time.Minute),
50+
)
51+
if err != nil {
52+
panic(err)
53+
}
54+
defer gateway.Close()
55+
56+
contract := gateway.GetNetwork("mychannel").GetContract("bigdatacc")
57+
3558
// Handle different functions
3659
if function == "update" {
37-
result, err := f.Update(function, variableName, change, sign)
60+
result, err := f.Update(contract, function, variableName, change, sign)
3861
if err != nil {
3962
log.Fatalf("error: %v", err)
4063
}
4164
log.Println("Value of variable", string(variableName), ": ", string(result))
4265

4366
} else if function == "delete" || function == "prune" || function == "delstandard" {
44-
result, err := f.DeletePrune(function, variableName)
67+
result, err := f.DeletePrune(contract, function, variableName)
4568
if err != nil {
4669
log.Fatalf("error: %v", err)
4770
}
4871
log.Println(string(result))
4972
} else if function == "get" || function == "getstandard" {
50-
result, err := f.Query(function, variableName)
73+
result, err := f.Query(contract, function, variableName)
5174
if err != nil {
5275
log.Fatalf("error: %v", err)
5376
}
5477
log.Println("Value of variable", string(variableName), ": ", string(result))
5578
} else if function == "manyUpdates" {
5679
log.Println("submitting 1000 concurrent updates...")
57-
result, err := f.ManyUpdates("update", variableName, change, sign)
80+
result, err := f.ManyUpdates(contract, "update", variableName, change, sign)
5881
if err != nil {
5982
log.Fatalf("error: %v", err)
6083
}
6184
log.Println("Final value of variable", string(variableName), ": ", string(result))
6285
} else if function == "manyUpdatesTraditional" {
6386
log.Println("submitting 1000 concurrent updates...")
64-
result, err := f.ManyUpdates("putstandard", variableName, change, sign)
87+
result, err := f.ManyUpdates(contract, "putstandard", variableName, change, sign)
6588
if err != nil {
6689
log.Fatalf("error: %v", err)
6790
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2025 IBM All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
import (
10+
"crypto/x509"
11+
"fmt"
12+
"os"
13+
"path"
14+
15+
"github.com/hyperledger/fabric-gateway/pkg/identity"
16+
"google.golang.org/grpc"
17+
"google.golang.org/grpc/credentials"
18+
)
19+
20+
const (
21+
mspID = "Org1MSP"
22+
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
23+
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts"
24+
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore"
25+
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
26+
peerEndpoint = "dns:///localhost:7051"
27+
gatewayPeer = "peer0.org1.example.com"
28+
)
29+
30+
// newGrpcConnection creates a gRPC connection to the Gateway server.
31+
func newGrpcConnection() *grpc.ClientConn {
32+
certificatePEM, err := os.ReadFile(tlsCertPath)
33+
if err != nil {
34+
panic(fmt.Errorf("failed to read TLS certificate file: %w", err))
35+
}
36+
37+
certificate, err := identity.CertificateFromPEM(certificatePEM)
38+
if err != nil {
39+
panic(err)
40+
}
41+
42+
certPool := x509.NewCertPool()
43+
certPool.AddCert(certificate)
44+
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
45+
46+
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
47+
if err != nil {
48+
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
49+
}
50+
51+
return connection
52+
}
53+
54+
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
55+
func newIdentity() *identity.X509Identity {
56+
certificatePEM, err := readFirstFile(certPath)
57+
if err != nil {
58+
panic(fmt.Errorf("failed to read certificate file: %w", err))
59+
}
60+
61+
certificate, err := identity.CertificateFromPEM(certificatePEM)
62+
if err != nil {
63+
panic(err)
64+
}
65+
66+
id, err := identity.NewX509Identity(mspID, certificate)
67+
if err != nil {
68+
panic(err)
69+
}
70+
71+
return id
72+
}
73+
74+
func loadCertificate(filename string) (*x509.Certificate, error) {
75+
certificatePEM, err := os.ReadFile(filename)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to read certificate file: %w", err)
78+
}
79+
return identity.CertificateFromPEM(certificatePEM)
80+
}
81+
82+
// newSign creates a function that generates a digital signature from a message digest using a private key.
83+
func newSign() identity.Sign {
84+
privateKeyPEM, err := readFirstFile(keyPath)
85+
if err != nil {
86+
panic(fmt.Errorf("failed to read private key file: %w", err))
87+
}
88+
89+
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
90+
if err != nil {
91+
panic(err)
92+
}
93+
94+
sign, err := identity.NewPrivateKeySign(privateKey)
95+
if err != nil {
96+
panic(err)
97+
}
98+
99+
return sign
100+
}
101+
102+
func readFirstFile(dirPath string) ([]byte, error) {
103+
dir, err := os.Open(dirPath)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
fileNames, err := dir.Readdirnames(1)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
return os.ReadFile(path.Join(dirPath, fileNames[0]))
114+
}

0 commit comments

Comments
 (0)