Fix high-throughput sample (#1351)

Build failures because the startFabric.sh script specified that the
chaincode required initialization, which is both legacy behavior and is
unnecessary.

The chaincode is updated to use the Contract API instead of the legacy /
low-level chaincode API. The client application is also simplified.

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
Mark S. Lewis 2025-10-06 13:36:09 +01:00 committed by GitHub
parent ad1950554d
commit 6130e1b9e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 297 additions and 379 deletions

View file

@ -35,9 +35,14 @@ jobs:
go run . manyUpdates testvar1 100 +
go run . prune testvar1
go run . get testvar1
go run . update testvar2 100 +
go run . update testvar1 100 +
go run . get testvar1
go run . delete testvar1
go run . manyUpdatesTraditional testvar2 100 +
go run . delete testvar2
go run . getstandard testvar2
go run . updatestandard testvar2 100 +
go run . getstandard testvar2
go run . delstandard testvar2
- name: Stop Fabric
working-directory: high-throughput

View file

@ -35,7 +35,7 @@ Hyperledger Fabric, however, careful considerations need to be given to the data
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
### 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,
@ -59,7 +59,7 @@ eventually be committed to the ledger in the order they were received. Additiona
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.
### 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
@ -167,29 +167,19 @@ The application will query the variable after submitting the transaction. The re
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 . 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 . manyUpdatesTraditional testvar2 100 +
```
When the program ends, you may see that none of the updates succeeded.
When the program ends, you should see that many of the updates failed and that the value is much lower than expected.
```
2020/10/27 18:03:15 Final value of variable testvar2 : 100
2025/09/12 10:59:30 submitting 1000 concurrent updates...
2025/09/12 10:59:31 998 submit failures.
2025/09/12 10:59:31 Value of variable testvar2: 200
```
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.
The transactions failed because multiple transactions in each block updated the same key concurrently. Because of this, these transactions generated read/write conflicts and 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:

View file

@ -9,11 +9,14 @@ package main
import (
"log"
"os"
"sync"
"sync/atomic"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
f "github.com/hyperledger/fabric-samples/high-throughput/application-go/functions"
gatewaypb "github.com/hyperledger/fabric-protos-go-apiv2/gateway"
"google.golang.org/grpc/status"
)
func main() {
@ -22,7 +25,7 @@ func main() {
if len(os.Args) <= 2 {
log.Println("Usage: function variableName")
log.Fatalf("functions: update manyUpdates manyUpdatesTraditional get prune delete")
log.Fatalf("functions: update delete prune get manyUpdates updatestandard delstandard getstandard manyUpdatesTraditional")
} 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 {
@ -56,38 +59,117 @@ func main() {
contract := gateway.GetNetwork("mychannel").GetContract("bigdatacc")
// Handle different functions
if function == "update" {
result, err := f.Update(contract, 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(contract, function, variableName)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println(string(result))
} else if function == "get" || function == "getstandard" {
result, err := f.Query(contract, 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(contract, "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(contract, "putstandard", variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Final value of variable", string(variableName), ": ", string(result))
switch function {
case "update":
update(contract, variableName, change, sign)
case "updatestandard":
updateStandard(contract, variableName, change, sign)
case "delete":
delete(contract, variableName)
case "prune":
prune(contract, variableName)
case "delstandard":
delStandard(contract, variableName)
case "get":
get(contract, variableName)
case "getstandard":
getStandard(contract, variableName)
case "manyUpdates":
manyUpdates(contract, "Update", variableName, change, sign)
get(contract, variableName)
case "manyUpdatesTraditional":
manyUpdates(contract, "UpdateStandard", variableName, change, sign)
getStandard(contract, variableName)
default:
log.Fatalln("Unkown function:", function)
}
}
func update(contract *client.Contract, variableName, change, sign string) {
result, err := contract.SubmitTransaction("Update", variableName, change, sign)
failOnError(err)
log.Println(string(result))
get(contract, variableName)
}
func updateStandard(contract *client.Contract, variableName, change, sign string) {
result, err := contract.SubmitTransaction("UpdateStandard", variableName, change, sign)
failOnError(err)
log.Printf("Value of variable %s: %s\n", variableName, result)
}
func delete(contract *client.Contract, variableName string) {
result, err := contract.SubmitTransaction("Delete", variableName)
failOnError(err)
log.Println(string(result))
}
func prune(contract *client.Contract, variableName string) {
result, err := contract.SubmitTransaction("Prune", variableName)
failOnError(err)
log.Println(string(result))
}
func delStandard(contract *client.Contract, variableName string) {
_, err := contract.SubmitTransaction("DelStandard", variableName)
failOnError(err)
log.Printf("Deleted %s.\n", variableName)
}
func get(contract *client.Contract, variableName string) {
result, err := contract.EvaluateTransaction("Get", variableName)
failOnError(err)
log.Printf("Value of variable %s: %s\n", variableName, result)
}
func getStandard(contract *client.Contract, variableName string) {
result, err := contract.EvaluateTransaction("GetStandard", variableName)
failOnError(err)
log.Printf("Value of variable %s: %s\n", variableName, result)
}
func manyUpdates(contract *client.Contract, function, variableName, change, sign string) {
log.Println("submitting 1000 concurrent updates...")
var failCount atomic.Uint32
var wg sync.WaitGroup
for range 1000 {
wg.Add(1)
go func() {
defer wg.Done()
_, err := contract.SubmitTransaction(function, variableName, change, sign)
if err != nil {
failCount.Add(1)
}
}()
}
wg.Wait()
n := failCount.Load()
if n > 0 {
log.Printf("%v submit failures.", n)
}
}
func failOnError(err error) {
if err == nil {
return
}
details := status.Convert(err).Details()
if len(details) > 0 {
log.Println("Error Details:")
for _, detail := range details {
switch detail := detail.(type) {
case *gatewaypb.ErrorDetail:
log.Printf("- address: %s\n mspId: %s\n message: %s\n", detail.Address, detail.MspId, detail.Message)
default:
log.Printf("- %s", detail)
}
}
}
log.Fatalf("error: %v", err)
}

View file

@ -1,22 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// DeletePrune deletes or prunes a variable
func DeletePrune(contract *client.Contract, function, variableName string) ([]byte, error) {
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,39 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"sync"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// ManyUpdates allows you to push many cuncurrent updates to a variable
func ManyUpdates(contract *client.Contract, function, variableName, change, sign string) ([]byte, error) {
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,22 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// Query can be used to read the latest value of a variable
func Query(contract *client.Contract, function, variableName string) ([]byte, error) {
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,27 +0,0 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// Update can be used to update or prune the variable
func Update(contract *client.Contract, function, variableName, change, sign string) ([]byte, error) {
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

@ -4,11 +4,11 @@ go 1.23.0
require (
github.com/hyperledger/fabric-gateway v1.8.0
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7
google.golang.org/grpc v1.73.0
)
require (
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.42.0 // indirect

View file

@ -2,17 +2,25 @@ module github.com/hyperledger/fabric-samples/high-throughput/chaincode
go 1.23.0
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a
github.com/hyperledger/fabric-protos-go v0.3.0
)
require github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/grpc v1.67.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,33 +1,61 @@
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a h1:HwSCxEeiBthwcazcAykGATQ36oG9M+HEQvGLvB7aLvA=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a/go.mod h1:TDSu9gxURldEnaGSFbH1eMlfSQBWQcMQfnDBcpQv5lU=
github.com/hyperledger/fabric-protos-go v0.3.0 h1:MXxy44WTMENOh5TI8+PCK2x6pMj47Go2vFRKDHB2PZs=
github.com/hyperledger/fabric-protos-go v0.3.0/go.mod h1:WWnyWP40P2roPmmvxsUXSvVI/CF6vwY1K1UFidnKBys=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/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-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
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.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -27,55 +27,26 @@ package main
*/
import (
"fmt"
"log"
"strconv"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// 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)
func main() {
chaincode, err := contractapi.NewChaincode(&SmartContract{})
if err != nil {
log.Panicf("Error creating chaincode: %s", err)
}
return shim.Error("Invalid Smart Contract function name.")
if err := chaincode.Start(); err != nil {
log.Panicf("Error starting chaincode: %s", err)
}
}
// SmartContract is the data structure which represents this contract and its functions
type SmartContract struct {
contractapi.Contract
}
/**
@ -86,47 +57,36 @@ func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) pb.Response
* - 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
* Returns a response 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)
func (s *SmartContract) Update(ctx contractapi.TransactionContextInterface, name string, delta string, op string) (string, error) {
_, err := strconv.ParseFloat(delta, 64)
if err != nil {
return shim.Error("Provided value was not a number")
return "", fmt.Errorf("provided value was not a number: %s", delta)
}
// Make sure a valid operator is provided
if op != "+" && op != "-" {
return shim.Error(fmt.Sprintf("Operator %s is unrecognized", op))
return "", fmt.Errorf("operator %s is unrecognized", op)
}
// Retrieve info needed for the update procedure
txid := APIstub.GetTxID()
txid := ctx.GetStub().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})
compositeKey, compositeErr := ctx.GetStub().CreateCompositeKey(compositeIndexName, []string{name, op, delta, txid})
if compositeErr != nil {
return shim.Error(fmt.Sprintf("Could not create a composite key for %s: %s", name, compositeErr.Error()))
return "", fmt.Errorf("could not create a composite key for %s: %w", name, compositeErr)
}
// Save the composite key index
compositePutErr := APIstub.PutState(compositeKey, []byte{0x00})
compositePutErr := ctx.GetStub().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 "", fmt.Errorf("could not put operation for %s in the ledger: %w", name, compositePutErr)
}
return shim.Success([]byte(fmt.Sprintf("Successfully added %s%s to %s", op, args[1], name)))
return fmt.Sprintf("Successfully added %s%s to %s", op, delta, name), nil
}
/**
@ -135,44 +95,34 @@ func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []strin
* 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
* Returns a response 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]
func (s *SmartContract) Get(ctx contractapi.TransactionContextInterface, name string) (string, error) {
// Get all deltas for the variable
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
deltaResultsIterator, deltaErr := ctx.GetStub().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()))
return "", fmt.Errorf("could not retrieve value for %s: %w", name, deltaErr)
}
defer deltaResultsIterator.Close()
// Check the variable existed
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
return "", fmt.Errorf("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++ {
for deltaResultsIterator.HasNext() {
// Get the next row
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(nextErr.Error())
return "", nextErr
}
// Split the composite key into its component parts
_, keyParts, splitKeyErr := APIstub.SplitCompositeKey(responseRange.Key)
_, keyParts, splitKeyErr := ctx.GetStub().SplitCompositeKey(responseRange.Key)
if splitKeyErr != nil {
return shim.Error(splitKeyErr.Error())
return "", splitKeyErr
}
// Retrieve the delta value and operation
@ -182,7 +132,7 @@ func (s *SmartContract) get(APIstub shim.ChaincodeStubInterface, args []string)
// Convert the value string and perform the operation
value, convErr := strconv.ParseFloat(valueStr, 64)
if convErr != nil {
return shim.Error(convErr.Error())
return "", convErr
}
switch operation {
@ -191,11 +141,11 @@ func (s *SmartContract) get(APIstub shim.ChaincodeStubInterface, args []string)
case "-":
finalVal -= value
default:
return shim.Error(fmt.Sprintf("Unrecognized operation %s", operation))
return "", fmt.Errorf("unrecognized operation %s", operation)
}
}
return shim.Success([]byte(strconv.FormatFloat(finalVal, 'f', -1, 64)))
return strconv.FormatFloat(finalVal, 'f', -1, 64), nil
}
/**
@ -204,46 +154,35 @@ func (s *SmartContract) get(APIstub shim.ChaincodeStubInterface, args []string)
* 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
* Returns a response 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]
func (s *SmartContract) Prune(ctx contractapi.TransactionContextInterface, name string) (string, error) {
// Get all delta rows for the variable
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
deltaResultsIterator, deltaErr := ctx.GetStub().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()))
return "", fmt.Errorf("could not retrieve value for %s: %w", name, deltaErr)
}
defer deltaResultsIterator.Close()
// Check the variable existed
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
return "", fmt.Errorf("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++ {
for ; deltaResultsIterator.HasNext(); i++ {
// Get the next row
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(nextErr.Error())
return "", nextErr
}
// Split the key into its composite parts
_, keyParts, splitKeyErr := APIstub.SplitCompositeKey(responseRange.Key)
_, keyParts, splitKeyErr := ctx.GetStub().SplitCompositeKey(responseRange.Key)
if splitKeyErr != nil {
return shim.Error(splitKeyErr.Error())
return "", splitKeyErr
}
// Retrieve the operation and value
@ -253,13 +192,13 @@ func (s *SmartContract) prune(APIstub shim.ChaincodeStubInterface, args []string
// Convert the value to a float
value, convErr := strconv.ParseFloat(valueStr, 64)
if convErr != nil {
return shim.Error(convErr.Error())
return "", convErr
}
// Delete the row from the ledger
deltaRowDelErr := APIstub.DelState(responseRange.Key)
deltaRowDelErr := ctx.GetStub().DelState(responseRange.Key)
if deltaRowDelErr != nil {
return shim.Error(fmt.Sprintf("Could not delete delta row: %s", deltaRowDelErr.Error()))
return "", fmt.Errorf("could not delete delta row: %w", deltaRowDelErr)
}
// Add the value of the deleted row to the final aggregate
@ -269,17 +208,16 @@ func (s *SmartContract) prune(APIstub shim.ChaincodeStubInterface, args []string
case "-":
finalVal -= value
default:
return shim.Error(fmt.Sprintf("Unrecognized operation %s", operation))
return "", fmt.Errorf("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))
if updateMessage, err := s.Update(ctx, name, strconv.FormatFloat(finalVal, 'f', -1, 64), "+"); err != nil {
return "", fmt.Errorf("could not update the final value of the variable after pruning: %s", updateMessage)
}
return shim.Success([]byte(fmt.Sprintf("Successfully pruned variable %s, final value is %f, %d rows pruned", args[0], finalVal, i)))
return fmt.Sprintf("Successfully pruned variable %s, final value is %f, %d rows pruned", name, finalVal, i), nil
}
/**
@ -287,110 +225,91 @@ func (s *SmartContract) prune(APIstub shim.ChaincodeStubInterface, args []string
* 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
* Returns a response 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]
func (s *SmartContract) Delete(ctx contractapi.TransactionContextInterface, name string) (string, error) {
// Delete all delta rows
deltaResultsIterator, deltaErr := APIstub.GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
deltaResultsIterator, deltaErr := ctx.GetStub().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()))
return "", fmt.Errorf("could not retrieve delta rows for %s: %w", name, deltaErr)
}
defer deltaResultsIterator.Close()
// Ensure the variable exists
if !deltaResultsIterator.HasNext() {
return shim.Error(fmt.Sprintf("No variable by the name %s exists", name))
return "", fmt.Errorf("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++ {
for ; deltaResultsIterator.HasNext(); i++ {
responseRange, nextErr := deltaResultsIterator.Next()
if nextErr != nil {
return shim.Error(fmt.Sprintf("Could not retrieve next delta row: %s", nextErr.Error()))
return "", fmt.Errorf("could not retrieve next delta row: %w", nextErr)
}
deltaRowDelErr := APIstub.DelState(responseRange.Key)
deltaRowDelErr := ctx.GetStub().DelState(responseRange.Key)
if deltaRowDelErr != nil {
return shim.Error(fmt.Sprintf("Could not delete delta row: %s", deltaRowDelErr.Error()))
return "", fmt.Errorf("could not delete delta row: %w", deltaRowDelErr)
}
}
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)
}
return fmt.Sprintf("Deleted %s, %d rows removed", name, i), nil
}
/**
* 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()))
func (s *SmartContract) UpdateStandard(ctx contractapi.TransactionContextInterface, name string, delta string, operation string) (float64, error) {
deltaValue, err := strconv.ParseFloat(delta, 64)
if err != nil {
return 0, err
}
putErr := APIstub.PutState(name, []byte(valStr))
if putErr != nil {
return shim.Error(fmt.Sprintf("Failed to put state: %s", putErr.Error()))
var currentValue float64
if valueBytes, err := ctx.GetStub().GetState(name); err == nil && len(valueBytes) > 0 {
currentValue, err = strconv.ParseFloat(string(valueBytes), 64)
if err != nil {
return 0, err
}
}
return shim.Success(nil)
switch operation {
case "+":
currentValue += deltaValue
case "-":
currentValue -= deltaValue
default:
return 0, fmt.Errorf("unrecognized operation %s", operation)
}
valueStr := strconv.FormatFloat(currentValue, 'f', -1, 64)
if err := ctx.GetStub().PutState(name, []byte(valueStr)); err != nil {
return 0, fmt.Errorf("failed to put state: %w", err)
}
return currentValue, 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()))
func (s *SmartContract) GetStandard(ctx contractapi.TransactionContextInterface, name string) (string, error) {
valueBytes, err := ctx.GetStub().GetState(name)
if err != nil {
return "", fmt.Errorf("failed to get state: %w", err)
}
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()))
value, err := strconv.ParseFloat(string(valueBytes), 64)
if err != nil {
return "", err
}
return shim.Success(nil)
return strconv.FormatFloat(value, 'f', -1, 64), nil
}
func (s *SmartContract) DelStandard(ctx contractapi.TransactionContextInterface, name string) error {
if err := ctx.GetStub().DelState(name); err != nil {
return fmt.Errorf("failed to delete state: %w", err)
}
return nil
}

View file

@ -11,7 +11,3 @@ set -ex
pushd ../test-network
./network.sh down
popd
rm -rf application-go/wallet/
rm -rf application-go/keystore/

View file

@ -19,7 +19,7 @@ pushd ../test-network
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
./network.sh deployCC -ccn bigdatacc -ccp ../high-throughput/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
popd
cat <<EOF