├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .vscode └── settings.json ├── CODEOWNERS ├── DCO.md ├── LICENSE ├── MAINTAINERS.md ├── NOTICE ├── README.md ├── ccapi ├── Dockerfile ├── chaincode │ ├── event.go │ ├── eventHandler.go │ ├── invoke.go │ ├── invokeGateway.go │ ├── query.go │ ├── queryGateway.go │ └── utils.go ├── common │ ├── abort.go │ ├── fabsdk.go │ ├── gateway.go │ └── resmgnt.go ├── config │ ├── configsdk-org.yaml │ ├── configsdk-org1.yaml │ ├── configsdk-org2.yaml │ └── configsdk-org3.yaml ├── docker-compose-1org.yaml ├── docker-compose.yaml ├── docs │ ├── docs.go │ └── swagger.yaml ├── go.mod ├── go.sum ├── handlers │ ├── invoke.go │ ├── invokeGateway.go │ ├── invokeV1.go │ ├── qscc.go │ ├── query.go │ ├── queryGateway.go │ └── queryV1.go ├── main.go ├── routes │ ├── chaincode.go │ ├── routes.go │ └── sdk.go ├── server │ └── server.go └── web-client │ └── docker-compose-goinitus.yaml ├── chaincode ├── Dockerfile ├── META-INF │ ├── .gitkeep │ └── statedb │ │ └── couchdb │ │ └── indexes │ │ └── example.json ├── assetTypeList.go ├── assettypes │ ├── book.go │ ├── customAssets.go │ ├── dynamicAssetTypes.go │ ├── library.go │ ├── person.go │ └── secret.go ├── datatypes │ ├── bookType.go │ ├── cpf.go │ └── datatypes.go ├── eventTypeList.go ├── eventtypes │ └── createLibraryLog.go ├── generateCollections.go ├── go.mod ├── go.sum ├── header │ └── header.go ├── main.go ├── main_test.go ├── tests │ ├── features │ │ ├── createNewLibrary.feature │ │ ├── getBooksByAuthor.feature │ │ ├── getNumberOfBooksFromLibrary.feature │ │ └── updateBookTentant.feature │ ├── main_test.go │ └── request_test.go ├── txList.go ├── txdefs │ ├── createNewLibrary.go │ ├── getBooksByAuthor.go │ ├── getNumberOfBooksFromLibrary.go │ └── updateBookTentant.go ├── txdefs_createNewLibrary_test.go ├── txdefs_getNumberOfBooksFromLibrary_test.go └── txdefs_updateBookTenant_test.go ├── fabric-private-chaincode └── README.md ├── fabric ├── .gitignore ├── addOrg3 │ ├── .env │ ├── README.md │ ├── addOrg3.sh │ ├── ccp-generate.sh │ ├── ccp-template.json │ ├── ccp-template.yaml │ ├── configtx.yaml │ ├── docker │ │ ├── docker-compose-ca-org3.yaml │ │ ├── docker-compose-couch-org3.yaml │ │ └── docker-compose-org3.yaml │ ├── fabric-ca │ │ ├── org3 │ │ │ └── fabric-ca-server-config.yaml │ │ └── registerEnroll.sh │ └── org3-crypto.yaml ├── config │ ├── configtx.yaml │ ├── core.yaml │ └── orderer.yaml ├── configtx-1org │ └── configtx.yaml ├── configtx │ └── configtx.yaml ├── docker │ ├── docker-compose-ca.yaml │ ├── docker-compose-couch-org.yaml │ ├── docker-compose-couch.yaml │ ├── docker-compose-test-net-org.yaml │ └── docker-compose-test-net.yaml ├── externalBuilder │ └── bin │ │ ├── build │ │ ├── detect │ │ └── release ├── network.sh ├── organizations │ ├── ccp-generate.sh │ ├── ccp-template.json │ ├── ccp-template.yaml │ ├── cryptogen │ │ ├── crypto-config-orderer.yaml │ │ ├── crypto-config-org.yaml │ │ ├── crypto-config-org1.yaml │ │ ├── crypto-config-org2.yaml │ │ └── crypto-config-org3.yaml │ └── fabric-ca │ │ └── registerEnroll.sh ├── scripts │ ├── ccutils.sh │ ├── configUpdate.sh │ ├── createChannel.sh │ ├── deployCC.sh │ ├── deployCCAAS.sh │ ├── envVar.sh │ ├── org3-scripts │ │ ├── joinChannel.sh │ │ └── updateChannelConfig.sh │ ├── setAnchorPeer.sh │ └── utils.sh └── startDev.sh ├── generatePackage.sh ├── go.work ├── go.work.sum ├── scripts ├── generateCollection.sh ├── godog.sh ├── installPreReqUbuntu.sh ├── legacy │ ├── generateTar.sh │ └── generateTemplateTar2.sh ├── onlineTest.sh ├── reloadCCAPI.sh ├── renameProject.sh └── tryout.sh ├── startDev.sh ├── startWeb.sh └── upgradeCC.sh /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master, main, develop ] 6 | pull_request: 7 | branches: [ master, main, develop ] 8 | 9 | jobs: 10 | build: 11 | defaults: 12 | run: 13 | shell: bash 14 | working-directory: chaincode 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.21 23 | 24 | - name: Build 25 | run: go build -v github.com/hyperledger-labs/cc-tools-demo/chaincode 26 | 27 | - name: Unit tests 28 | run: go test github.com/hyperledger-labs/cc-tools-demo/chaincode -coverpkg=./... -v 29 | 30 | # - name: Integration tests 31 | # run: go test -v ./tests 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | fabric/crypto-config 4 | fabric/channel-artifacts 5 | fabric/ca 6 | fabric/bin 7 | fabric/builders 8 | chaincode/vendor/* 9 | chaincode/collections.json 10 | ccapi/vendor/* 11 | cc-tools-demo.tar.gz 12 | package-lock.json 13 | 14 | .DS_Store 15 | 16 | tmp -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testFlags": [ 3 | "-v", 4 | "-coverpkg=github.com/hyperledger-labs/cc-tools-demo/chaincode/..." 5 | ] 6 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hyperledger-labs/cc-tools-committers -------------------------------------------------------------------------------- /DCO.md: -------------------------------------------------------------------------------- 1 | # Probot: DCO 2 | 3 | a GitHub Integration built with [probot](https://github.com/probot/probot) that enforces the [Developer Certificate of Origin](https://developercertificate.org/) (DCO) on Pull Requests. It requires all commit messages to contain the `Signed-off-by` line with an email address that matches the commit author. 4 | 5 | ## Usage 6 | 7 | [Configure the integration](https://github.com/apps/dco) for your organization or repositories. Enable [required status checks](docs/required-statuses.md) if you want to enforce the DCO on all commits. 8 | 9 | See [docs/deploy.md](docs/deploy.md) if you would like to run your own instance of this plugin. 10 | 11 | ## Modes of operations 12 | 13 | ### Default 14 | 15 | By default, Probot DCO enforces the presence of [valid DCO signoffs](#how-it-works) on all commits (excluding bots and merges). If a PRs contains commits that lack a valid Signed-off-by line, they are blocked until a correctly signed-off revision of the commit is pushed. This closely mirrors the upstream Linux kernel process. 16 | 17 | ### Individual remediation commit support 18 | 19 | Optionally, a project can allow individual remediation commit support, where the failing commit's author can push an additional properly signed-off commit with additional text in the commit log that indicates they apply their signoff retroactively. 20 | 21 | To enable this, place the following configuration file in `.github/dco.yml` on the default branch: 22 | 23 | ```yaml 24 | allowRemediationCommits: 25 | individual: true 26 | ``` 27 | 28 | ### Third-party remediation support 29 | 30 | Additionally, a project can allow third-parties to sign off on an author's behalf by pushing an additional properly signed-off commit with additional text in the commit log that indicates they sign off on behalf of the author. Third-party remediation requires individual remediation to be enabled. 31 | 32 | To enable this, place the following configuration file in `.github/dco.yml` on the default branch: 33 | 34 | ```yaml 35 | allowRemediationCommits: 36 | individual: true 37 | thirdParty: true 38 | ``` 39 | 40 | ### Skipping sign-off for organization members 41 | 42 | It is possible to disable the check for commits authored and [signed](https://help.github.com/articles/signing-commits-using-gpg/) by members of the organization the repository belongs to. To do this, place the following configuration file in `.github/dco.yml` on the default branch: 43 | 44 | ```yaml 45 | require: 46 | members: false 47 | ``` 48 | 49 | When this setting is present on a repository that belongs to a GitHub user (instead of an organization), only the repository owner is allowed to push commits without sign-off. 50 | 51 | ## How it works 52 | 53 | The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full [text of the DCO](https://developercertificate.org/), reformatted for readability: 54 | 55 | > By making a contribution to this project, I certify that: 56 | > 57 | > a. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or 58 | > 59 | > b. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or 60 | > 61 | > c. The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. 62 | > 63 | > d. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. 64 | 65 | Contributors _sign-off_ that they adhere to these requirements by adding a `Signed-off-by` line to commit messages. 66 | 67 | ``` 68 | This is my commit message 69 | 70 | Signed-off-by: Random J Developer 71 | ``` 72 | 73 | Git even has a `-s` command line option to append this automatically to your commit message: 74 | 75 | ``` 76 | $ git commit -s -m 'This is my commit message' 77 | ``` 78 | 79 | Once [installed](#usage), this integration will create a [check](https://developer.github.com/v3/checks/runs/) indicating whether or not commits in a Pull Request do not contain a valid `Signed-off-by` line. 80 | 81 | ![DCO success](https://user-images.githubusercontent.com/13410355/42352738-35f4e690-8071-11e8-9c8c-260e5868bfc8.png) 82 | ![DCO failure](https://user-images.githubusercontent.com/13410355/42352794-85fe1c9c-8071-11e8-834a-05a4aeb8cc90.png) 83 | 84 | Additionally, the DCO creates an override button accessible to only those with write access to the repository to create a successful check. 85 | 86 | ![DCO button](https://user-images.githubusercontent.com/13410355/42353254-3bfa266a-8074-11e8-80b4-18760c5efeee.png) 87 | 88 | ## Further Reading 89 | 90 | If you want to learn more about the DCO and why it might be necessary, here are some good resources: 91 | 92 | - [Developer Certificate of Origin versus Contributor License Agreements](https://julien.ponge.org/blog/developer-certificate-of-origin-versus-contributor-license-agreements/) 93 | - [The most powerful contributor agreement](https://lwn.net/Articles/592503/) -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | ### **Active Maintainers** 4 | 5 | | Nome | Github | 6 | |:-------|:--------| 7 | | Samuel Venzi | [samuelvenzi](https://github.com/samuelvenzi) | 8 | | André Macedo | [andremacedopv](https://github.com/andremacedopv) | 9 | | Mikaella | [mikaellafs](https://github.com/mikaellafs) | 10 | | Marcos Sarres | [goledger](https://github.com/goledger) | 11 | | Lucas Campelo | [lucas-campelo](https://github.com/lucas-campelo) | 12 | | Jamille Peres | [jamillepp](https://github.com/jamillepp) | 13 | | Aline Lermen | [AlineLermen](https://github.com/AlineLermen) | 14 | 15 | 16 | ### **Retired Maintainers** 17 | | Nome | Github | 18 | |:-------|:--------| 19 | | Bruno Andreghetti | [bandreghetti](https://github.com/bandreghetti) | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | GoLedger CC-Tools Demo 2 | Copyright 2023 GoLedger Ltda. 3 | 4 | This product includes software developed at 5 | GoLedger (https://goledger.io/). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Labs CC Tools Demo Chaincode 2 | 3 | ## Directory Structure 4 | 5 | - `/fabric`: Fabric network v2.5 used as a test environment 6 | - `/chaincode`: chaincode-related files 7 | - `/ccapi`: chaincode REST API in Golang project 8 | - `/fabric-private-chaincode`: Explaining the integration project between CC-tools and FPC 9 | 10 | ## Development 11 | 12 | The `cc-tools` library has been tested in Fabric v2.2, v2.4 and v2.5 networks. 13 | 14 | Dependencies for chaincode and chaincode API: 15 | 16 | - Go 1.21 or higher 17 | 18 | Dependencies for test environment: 19 | 20 | - Docker 20.10.5 or higher 21 | - Docker Compose 1.28.5 or higher 22 | 23 | Intallation of the Chaincode API Go: 24 | 25 | ```bash 26 | $ cd chaincode; go mod vendor; cd .. 27 | $ cd ccapi; go mod vendor; cd .. 28 | ``` 29 | 30 | 31 | ## Deploying test environment 32 | 33 | After installing, use the script `./startDev.sh` in the root folder to start the development environment. It will 34 | start all components of the project with 3 organizations. 35 | 36 | If you want to deploy with 1 organization, run the command `./startDev.sh -n 1`. 37 | 38 | To apply chaincode changes, run `$ ./upgradeCC.sh ` with a version higher than the current one (starts with 0.1). Append `-n 1` to the command if running with 1 organization. 39 | 40 | To apply CC API changes, run `$ ./scripts/reloadCCAPI.sh`. 41 | 42 | ## Deploying Chaincode as a service 43 | 44 | After installing, use the script `./startDev.sh -ccaas` in the root folder to start the development environment. It will 45 | start all components of the project with 3 organizations. 46 | 47 | If you want to deploy with 1 organization, run the command `./startDev.sh -ccaas -n 1`. 48 | 49 | To apply chaincode changes, run `$ ./upgradeCC.sh -ccaas ` with a version higher than the current one (starts with 0.1). Append `-n 1` to the command if running with 1 organization. 50 | 51 | To apply CC API changes, run `$ ./scripts/reloadCCAPI.sh`. 52 | 53 | ## Automated tryout and test 54 | 55 | To test transactions after starting all components, run `$ ./scripts/tryout.sh`. 56 | 57 | To test transactions using the godog tool, run `$ ./scripts/godog.sh`. 58 | 59 | 60 | ## Generate TAR archive for the chaincode 61 | 62 | The `generatePackage.sh` script is available to generate a `tar.gz` archive of the chaincode. 63 | 64 | By running `$ ./generatePackage.sh` without any option, the script generates a `collections.json` file for the private data on the chaincode with all the organizations defined on the readers section of private asset types, and then archives the code without the CCAPI. 65 | 66 | By using the `--org/-o` option along the script, it's possible to specify the organizations to be considered when generating the `collections.json` file. This option may be used multiple times to add all the organizations, ex: `$ ./generatePackage.sh -o org1MSP -o org2MSP`. 67 | 68 | By standard the archive is created using the project name with *1.0* label, to change it the `--name/-n` and `--label/-l` flags may be used. Example: `$ ./generatePackage.sh -n my-project -l 2.0` 69 | 70 | ## Integration with Fabric Private Chaincode 71 | 72 | If you want to execute your chaincode in a Trusted Execution Environment (TEE) using Fabric Private Chaincode (FPC), we've set up an integration guide to help you. Check out the instructions in the `./fabric-private-chaincode` directory to seamlessly integrate FPC with CC Tools for enhanced privacy and security. 73 | 74 | ## More 75 | 76 | You can reach GoLedger developers and `cc-tools` maintainers at our Discord - [Join us!](https://discord.gg/GndkYHxNyQ) 77 | 78 | More documentation and details on `cc-tools` can be found at [https://goledger-cc-tools.readthedocs.io/en/latest/](https://goledger-cc-tools.readthedocs.io/en/latest/) 79 | -------------------------------------------------------------------------------- /ccapi/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Golang runtime as a parent image 2 | FROM golang:1.18-alpine AS build 3 | 4 | # Set the working directory to /rest-server 5 | WORKDIR /rest-server 6 | 7 | # Copy the current directory contents into the container at /rest-server 8 | COPY . . 9 | 10 | RUN go mod download 11 | 12 | # Build the Go ccapi 13 | RUN go build -o ccapi 14 | 15 | # Use an official Alpine runtime as a parent image 16 | FROM alpine:latest 17 | 18 | ENV PATH="${PATH}:/usr/bin/" 19 | 20 | RUN apk update 21 | 22 | RUN apk add --no-cache \ 23 | docker \ 24 | openrc \ 25 | git \ 26 | gcc \ 27 | gcompat \ 28 | libc-dev \ 29 | libc6-compat \ 30 | libstdc++ && \ 31 | ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 32 | 33 | # Set the working directory to /rest-server 34 | WORKDIR /rest-server 35 | 36 | # Copy the ccapi binary from the build container to the current directory in the Alpine container 37 | COPY --from=build /rest-server/ccapi /usr/bin/ccapi 38 | 39 | # Run the ccapi binary 40 | CMD ["ccapi"] -------------------------------------------------------------------------------- /ccapi/chaincode/event.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "regexp" 9 | 10 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 11 | ev "github.com/hyperledger/fabric-sdk-go/pkg/client/event" 12 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 13 | ) 14 | 15 | func getEventClient(channelName string) (*ev.Client, error) { 16 | // create channel manager 17 | fabMngr, err := common.NewFabricChClient(channelName, os.Getenv("USER"), os.Getenv("ORG")) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | // Create event client 23 | ec, err := ev.New(fabMngr.Provider, ev.WithBlockEvents()) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return ec, nil 29 | } 30 | 31 | func WaitForEvent(channelName, ccName, eventName string, fn func(*fab.CCEvent)) { 32 | ec, err := getEventClient(channelName) 33 | if err != nil { 34 | log.Println("error getting event client: ", err) 35 | return 36 | } 37 | 38 | for { 39 | // Register chaincode event 40 | registration, notifier, err := ec.RegisterChaincodeEvent(ccName, eventName) 41 | if err != nil { 42 | log.Println("error registering chaincode event: ", err) 43 | return 44 | } 45 | 46 | // Execute handler function on event notification 47 | ccEvent := <-notifier 48 | fmt.Printf("Received CC event: %v\n", ccEvent) 49 | fn(ccEvent) 50 | 51 | ec.Unregister(registration) 52 | } 53 | } 54 | 55 | func HandleEvent(channelName, ccName string, event EventHandler) { 56 | ec, err := getEventClient(channelName) 57 | if err != nil { 58 | log.Println("error getting event client: ", err) 59 | return 60 | } 61 | 62 | for { 63 | // Register chaincode event 64 | registration, notifier, err := ec.RegisterChaincodeEvent(ccName, event.Tag) 65 | if err != nil { 66 | log.Println("error registering chaincode event: ", err) 67 | return 68 | } 69 | 70 | // Execute handler function on event notification 71 | ccEvent := <-notifier 72 | fmt.Printf("Received CC event: %v\n", ccEvent) 73 | event.Execute(ccEvent) 74 | 75 | ec.Unregister(registration) 76 | } 77 | } 78 | 79 | func RegisterForEvents() { 80 | // Get registered events on the chaincode 81 | res, _, err := Invoke(os.Getenv("CHANNEL"), os.Getenv("CCNAME"), "getEvents", os.Getenv("USER"), nil, nil) 82 | if err != nil { 83 | fmt.Println("error registering for events: ", err) 84 | return 85 | } 86 | 87 | var events []interface{} 88 | nerr := json.Unmarshal(res.Payload, &events) 89 | if nerr != nil { 90 | fmt.Println("error unmarshalling events: ", nerr) 91 | return 92 | } 93 | 94 | msp := common.GetClientOrg() + "MSP" 95 | 96 | for _, event := range events { 97 | eventMap := event.(map[string]interface{}) 98 | receiverArr, ok := eventMap["receivers"] 99 | 100 | isReceiver := true 101 | // Verify if the MSP is a receiver for the event 102 | if ok { 103 | isReceiver = false 104 | receivers := receiverArr.([]interface{}) 105 | for _, r := range receivers { 106 | receiver := r.(string) 107 | 108 | if len(receiver) <= 1 { 109 | continue 110 | } 111 | if receiver[0] == '$' { 112 | match, err := regexp.MatchString(receiver[1:], msp) 113 | if err != nil { 114 | fmt.Println("error matching regexp: ", err) 115 | return 116 | } 117 | if match { 118 | isReceiver = true 119 | break 120 | } 121 | } else { 122 | if receiver == msp { 123 | isReceiver = true 124 | break 125 | } 126 | } 127 | } 128 | } 129 | 130 | if isReceiver { 131 | eventHandler := EventHandler{ 132 | Tag: eventMap["tag"].(string), 133 | Type: EventType(eventMap["type"].(float64)), 134 | Transaction: eventMap["transaction"].(string), 135 | Channel: eventMap["channel"].(string), 136 | Chaincode: eventMap["chaincode"].(string), 137 | Label: eventMap["label"].(string), 138 | BaseLog: eventMap["baseLog"].(string), 139 | ReadOnly: eventMap["readOnly"].(bool), 140 | } 141 | 142 | go HandleEvent(os.Getenv("CHANNEL"), os.Getenv("CCNAME"), eventHandler) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ccapi/chaincode/eventHandler.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | b64 "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 10 | ) 11 | 12 | type EventType float64 13 | 14 | const ( 15 | EventLog EventType = iota 16 | EventTransaction 17 | EventCustom 18 | ) 19 | 20 | type EventHandler struct { 21 | Tag string 22 | Label string 23 | Type EventType 24 | Transaction string 25 | Channel string 26 | Chaincode string 27 | BaseLog string 28 | ReadOnly bool 29 | } 30 | 31 | func (event EventHandler) Execute(ccEvent *fab.CCEvent) { 32 | if len(event.BaseLog) > 0 { 33 | fmt.Println(event.BaseLog) 34 | } 35 | 36 | if event.Type == EventLog { 37 | var logStr string 38 | nerr := json.Unmarshal(ccEvent.Payload, &logStr) 39 | if nerr != nil { 40 | fmt.Println("error unmarshalling log: ", nerr) 41 | return 42 | } 43 | 44 | if len(logStr) > 0 { 45 | fmt.Println("Event '", event.Label, "' log: ", logStr) 46 | } 47 | } else if event.Type == EventTransaction { 48 | ch := os.Getenv("CHANNEL") 49 | if event.Channel != "" { 50 | ch = event.Channel 51 | } 52 | cc := os.Getenv("CCNAME") 53 | if event.Chaincode != "" { 54 | cc = event.Chaincode 55 | } 56 | 57 | res, _, err := Invoke(ch, cc, event.Transaction, os.Getenv("USER"), [][]byte{ccEvent.Payload}, nil) 58 | if err != nil { 59 | fmt.Println("error invoking transaction: ", err) 60 | return 61 | } 62 | 63 | var response map[string]interface{} 64 | nerr := json.Unmarshal(res.Payload, &response) 65 | if nerr != nil { 66 | fmt.Println("error unmarshalling response: ", nerr) 67 | return 68 | } 69 | fmt.Println("Response: ", response) 70 | } else if event.Type == EventCustom { 71 | // Encode payload to base64 72 | b64Encode := b64.StdEncoding.EncodeToString([]byte(ccEvent.Payload)) 73 | 74 | args, ok := json.Marshal(map[string]interface{}{ 75 | "eventTag": event.Tag, 76 | "payload": b64Encode, 77 | }) 78 | if ok != nil { 79 | fmt.Println("failed to encode args to JSON format") 80 | return 81 | } 82 | 83 | // Invoke tx 84 | txName := "executeEvent" 85 | if event.ReadOnly { 86 | txName = "runEvent" 87 | } 88 | 89 | _, _, err := Invoke(os.Getenv("CHANNEL"), os.Getenv("CCNAME"), txName, os.Getenv("USER"), [][]byte{args}, nil) 90 | if err != nil { 91 | fmt.Println("error invoking transaction: ", err) 92 | return 93 | } 94 | } else { 95 | fmt.Println("Event type not supported") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ccapi/chaincode/invoke.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry" 10 | ) 11 | 12 | func Invoke(channelName, ccName, txName, user string, txArgs [][]byte, transientRequest []byte) (*channel.Response, int, error) { 13 | // create channel manager 14 | fabMngr, err := common.NewFabricChClient(channelName, user, os.Getenv("ORG")) 15 | if err != nil { 16 | return nil, http.StatusInternalServerError, err 17 | } 18 | 19 | // Execute chaincode with channel's client 20 | rq := channel.Request{ChaincodeID: ccName, Fcn: txName} 21 | if len(txArgs) > 0 { 22 | rq.Args = txArgs 23 | } 24 | 25 | if len(transientRequest) != 0 { 26 | transientMap := make(map[string][]byte) 27 | transientMap["@request"] = transientRequest 28 | rq.TransientMap = transientMap 29 | } 30 | 31 | res, err := fabMngr.Client.Execute(rq, channel.WithRetry(retry.DefaultChannelOpts)) 32 | if err != nil { 33 | return nil, extractStatusCode(err.Error()), err 34 | } 35 | 36 | return &res, http.StatusOK, nil 37 | } 38 | -------------------------------------------------------------------------------- /ccapi/chaincode/invokeGateway.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 7 | "github.com/hyperledger/fabric-gateway/pkg/client" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | func InvokeGateway(channelName, chaincodeName, txName, user string, args []string, transientArgs []byte, endorsingOrgs []string) ([]byte, error) { 12 | // Gateway endpoint 13 | endpoint := os.Getenv("FABRIC_GATEWAY_ENDPOINT") 14 | 15 | // Create client grpc connection 16 | grpcConn, err := common.CreateGrpcConnection(endpoint) 17 | if err != nil { 18 | return nil, errors.Wrap(err, "failed to create grpc connection") 19 | } 20 | defer grpcConn.Close() 21 | 22 | // Create gateway connection 23 | gw, err := common.CreateGatewayConnection(grpcConn, user) 24 | if err != nil { 25 | return nil, errors.Wrap(err, "failed to create gateway connection") 26 | } 27 | defer gw.Close() 28 | 29 | // Obtain smart contract deployed on the network. 30 | network := gw.GetNetwork(channelName) 31 | contract := network.GetContract(chaincodeName) 32 | 33 | // Make transient request 34 | transientMap := make(map[string][]byte) 35 | transientMap["@request"] = transientArgs 36 | 37 | // Invoke transaction 38 | if transientArgs != nil && len(endorsingOrgs) > 0 { 39 | return contract.Submit(txName, 40 | client.WithArguments(args...), 41 | client.WithTransient(transientMap), 42 | client.WithEndorsingOrganizations(endorsingOrgs...), 43 | ) 44 | } 45 | 46 | if transientArgs != nil { 47 | return contract.Submit(txName, 48 | client.WithArguments(args...), 49 | client.WithTransient(transientMap), 50 | ) 51 | } 52 | 53 | if len(endorsingOrgs) > 0 { 54 | return contract.Submit(txName, 55 | client.WithArguments(args...), 56 | client.WithEndorsingOrganizations(endorsingOrgs...), 57 | ) 58 | } 59 | 60 | return contract.SubmitTransaction(txName, args...) 61 | } 62 | -------------------------------------------------------------------------------- /ccapi/chaincode/query.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry" 10 | ) 11 | 12 | func Query(channelName, ccName, txName, user string, txArgs [][]byte) (*channel.Response, int, error) { 13 | // create channel manager 14 | fabMngr, err := common.NewFabricChClient(channelName, user, os.Getenv("ORG")) 15 | if err != nil { 16 | return nil, http.StatusInternalServerError, err 17 | } 18 | 19 | // Execute chaincode with channel's client 20 | rq := channel.Request{ChaincodeID: ccName, Fcn: txName} 21 | if len(txArgs) > 0 { 22 | rq.Args = txArgs 23 | } 24 | 25 | res, err := fabMngr.Client.Query(rq, channel.WithRetry(retry.DefaultChannelOpts)) 26 | if err != nil { 27 | status := extractStatusCode(err.Error()) 28 | return nil, status, err 29 | } 30 | 31 | return &res, http.StatusOK, nil 32 | } 33 | -------------------------------------------------------------------------------- /ccapi/chaincode/queryGateway.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func QueryGateway(channelName, chaincodeName, txName, user string, args []string) ([]byte, error) { 11 | // Gateway endpoint 12 | endpoint := os.Getenv("FABRIC_GATEWAY_ENDPOINT") 13 | 14 | // Create client grpc connection 15 | grpcConn, err := common.CreateGrpcConnection(endpoint) 16 | if err != nil { 17 | return nil, errors.Wrap(err, "failed to create grpc connection") 18 | } 19 | defer grpcConn.Close() 20 | 21 | // Create gateway connection 22 | gw, err := common.CreateGatewayConnection(grpcConn, user) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "failed to create gateway connection") 25 | } 26 | defer gw.Close() 27 | 28 | // Obtain smart contract deployed on the network. 29 | network := gw.GetNetwork(channelName) 30 | contract := network.GetContract(chaincodeName) 31 | 32 | // Query transaction 33 | if len(args) == 0 { 34 | return contract.EvaluateTransaction(txName) 35 | } 36 | 37 | return contract.EvaluateTransaction(txName, args...) 38 | } 39 | -------------------------------------------------------------------------------- /ccapi/chaincode/utils.go: -------------------------------------------------------------------------------- 1 | package chaincode 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | func extractStatusCode(msg string) int { 11 | re := regexp.MustCompile(`Code:\s*\((\d+)\)`) 12 | 13 | matches := re.FindStringSubmatch(msg) 14 | if len(matches) == 0 { 15 | fmt.Println("No status code found in message") 16 | return http.StatusInternalServerError 17 | } 18 | 19 | statusCode, err := strconv.Atoi(matches[1]) 20 | if err != nil { 21 | fmt.Println("Failed to parse string to int when extracting status code") 22 | return http.StatusInternalServerError 23 | } 24 | 25 | return statusCode 26 | } 27 | -------------------------------------------------------------------------------- /ccapi/common/abort.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Abort(c *gin.Context, status int, err error) { 10 | c.JSON(status, gin.H{ 11 | "status": status, 12 | "error": err.Error(), 13 | }) 14 | c.Error(err) 15 | } 16 | 17 | func Respond(c *gin.Context, res interface{}, status int, err error) { 18 | if err != nil { 19 | c.JSON(status, gin.H{ 20 | "response": res, 21 | "status": status, 22 | "error": err.Error(), 23 | }) 24 | c.Error(err) 25 | return 26 | } 27 | 28 | c.JSON(http.StatusOK, res) 29 | } 30 | -------------------------------------------------------------------------------- /ccapi/common/fabsdk.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/core/config" 10 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 11 | ) 12 | 13 | type sdk struct { 14 | // sdk belongs to org defined in the configsdk.yaml file 15 | Sdk *fabsdk.FabricSDK 16 | Path string 17 | } 18 | 19 | // CreateContext allows creation of transactions using the supplied identity as the credential. 20 | func (s *sdk) CreateClientContext(options ...fabsdk.ContextOption) context.ClientProvider { 21 | return s.Sdk.Context(options...) 22 | } 23 | 24 | func (s *sdk) CreateChannelContext(channelName string, options ...fabsdk.ContextOption) context.ChannelProvider { 25 | return s.Sdk.ChannelContext(channelName, options...) 26 | } 27 | 28 | // Log config path which sdk was created 29 | func (s *sdk) LogPath() { 30 | log.Printf("sdk created from '%s'", s.Path) 31 | } 32 | 33 | // Singleton sdk instance 34 | var instance *sdk 35 | 36 | // GetSDK returns a fabric sdk instance. 37 | // 38 | // A new sdk is created if: 39 | // - it is the first time it is beeing used, or 40 | // - new sdk options are given 41 | // 42 | // Otherwise, it returns the one previoulsy created. 43 | // If options are given, the new sdk is not a singleton, and must 44 | // be closed by whoever invoked it. 45 | // 46 | // The configsdk file can be set via environment variable and defaults 47 | // to './config/configsdk.yaml' 48 | func GetSDK(sdkOpts ...fabsdk.Option) (*sdk, error) { 49 | 50 | // return new sdk instance if sdkOpts are given. 51 | // user must close sdk 52 | if len(sdkOpts) != 0 { 53 | cfgPath := getCfgPath() 54 | configOpt := config.FromFile(cfgPath) 55 | s, err := fabsdk.New(configOpt, sdkOpts...) 56 | 57 | return &sdk{ 58 | Sdk: s, 59 | Path: cfgPath, 60 | }, err 61 | } 62 | 63 | if instance == nil { 64 | cfgPath := getCfgPath() 65 | configOpt := config.FromFile(cfgPath) 66 | s, err := fabsdk.New(configOpt) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | instance = &sdk{ 72 | Sdk: s, 73 | Path: cfgPath, 74 | } 75 | instance.LogPath() 76 | } 77 | 78 | return instance, nil 79 | } 80 | 81 | // getCfgPath parses path for the configsdk 82 | // from environmet, and defaults to './config/configsdk.yaml' 83 | func getCfgPath() (cfgPath string) { 84 | cfgPath = os.Getenv("SDK_PATH") 85 | if cfgPath == "" { 86 | cfgPath = "./config/configsdk.yaml" 87 | } 88 | return 89 | } 90 | 91 | // GetClientOrg returns the name of the client organization 92 | func GetClientOrg() string { 93 | sdk, err := GetSDK() 94 | if err != nil { 95 | return "" 96 | } 97 | 98 | cfg, err := sdk.Sdk.Config() 99 | if err != nil { 100 | return "" 101 | } 102 | 103 | i, ok := cfg.Lookup("client") 104 | if !ok { 105 | return "" 106 | } 107 | m, ok := i.(map[string]interface{}) 108 | if !ok { 109 | return "" 110 | } 111 | 112 | org := m["organization"] 113 | orgName, ok := org.(string) 114 | if !ok { 115 | return "" 116 | } 117 | 118 | return orgName 119 | } 120 | 121 | func GetCryptoPath() string { 122 | sdk, err := GetSDK() 123 | if err != nil { 124 | return "" 125 | } 126 | 127 | cfg, err := sdk.Sdk.Config() 128 | if err != nil { 129 | return "" 130 | } 131 | 132 | i, ok := cfg.Lookup("client.cryptoconfig.path") 133 | if !ok { 134 | return "" 135 | } 136 | basePath, _ := i.(string) 137 | 138 | i, ok = cfg.Lookup(fmt.Sprintf("organizations.%s.cryptoPath", os.Getenv("ORG"))) 139 | if !ok { 140 | return "" 141 | } 142 | 143 | certPath, _ := i.(string) 144 | return basePath + "/" + certPath 145 | } 146 | 147 | func GetTLSCACert() string { 148 | sdk, err := GetSDK() 149 | if err != nil { 150 | return "" 151 | } 152 | 153 | cfg, err := sdk.Sdk.Config() 154 | if err != nil { 155 | return "" 156 | } 157 | 158 | i, ok := cfg.Lookup("client.tlsCerts.client.cacertfile") 159 | if !ok { 160 | return "" 161 | } 162 | 163 | certPath, _ := i.(string) 164 | return certPath 165 | } 166 | 167 | func GetMSPID() string { 168 | sdk, err := GetSDK() 169 | if err != nil { 170 | return "" 171 | } 172 | 173 | cfg, err := sdk.Sdk.Config() 174 | if err != nil { 175 | return "" 176 | } 177 | 178 | i, ok := cfg.Lookup(fmt.Sprintf("organizations.%s.mspid", os.Getenv("ORG"))) 179 | if !ok { 180 | return "" 181 | } 182 | 183 | mspid, _ := i.(string) 184 | return mspid 185 | } 186 | 187 | // Closes sdk instance if it was created 188 | func CloseSDK() { 189 | if instance != nil { 190 | instance.Sdk.Close() 191 | instance = nil 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /ccapi/common/gateway.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/hyperledger/fabric-gateway/pkg/client" 15 | "github.com/hyperledger/fabric-gateway/pkg/identity" 16 | "github.com/hyperledger/fabric-protos-go-apiv2/gateway" 17 | "github.com/pkg/errors" 18 | "google.golang.org/grpc" 19 | "google.golang.org/grpc/credentials" 20 | "google.golang.org/grpc/status" 21 | ) 22 | 23 | var ( 24 | gatewayTLSCredentials *credentials.TransportCredentials 25 | ) 26 | 27 | func CreateGrpcConnection(endpoint string) (*grpc.ClientConn, error) { 28 | // Check TLS credential was created 29 | if gatewayTLSCredentials == nil { 30 | gatewayServerName := os.Getenv("FABRIC_GATEWAY_NAME") 31 | 32 | cred, err := createTransportCredential(GetTLSCACert(), gatewayServerName) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "failed to create tls credentials") 35 | } 36 | 37 | gatewayTLSCredentials = &cred 38 | } 39 | 40 | // Create client grpc connection 41 | return grpc.Dial(endpoint, grpc.WithTransportCredentials(*gatewayTLSCredentials)) 42 | } 43 | 44 | func CreateGatewayConnection(grpcConn *grpc.ClientConn, user string) (*client.Gateway, error) { 45 | // Create identity 46 | id, err := newIdentity(getSignCert(user), GetMSPID()) 47 | if err != nil { 48 | return nil, errors.Wrap(err, "failed to create new identity") 49 | } 50 | gatewayId := id 51 | 52 | // Create sign function 53 | sign, err := newSign(getSignKey(user)) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "failed to create new sign function") 56 | } 57 | 58 | gatewaySign := sign 59 | 60 | // Create a Gateway connection for a specific client identity. 61 | return client.Connect( 62 | gatewayId, 63 | client.WithSign(gatewaySign), 64 | client.WithClientConnection(grpcConn), 65 | 66 | // Default timeouts for different gRPC calls 67 | client.WithEvaluateTimeout(5*time.Second), 68 | client.WithEndorseTimeout(15*time.Second), 69 | client.WithSubmitTimeout(5*time.Second), 70 | client.WithCommitStatusTimeout(1*time.Minute), 71 | ) 72 | } 73 | 74 | // Create transport credential 75 | func createTransportCredential(tlsCertPath, serverName string) (credentials.TransportCredentials, error) { 76 | certificate, err := loadCertificate(tlsCertPath) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | certPool := x509.NewCertPool() 82 | certPool.AddCert(certificate) 83 | return credentials.NewClientTLSFromCert(certPool, serverName), nil 84 | } 85 | 86 | // Creates a client identity for a gateway connection using an X.509 certificate. 87 | func newIdentity(certPath, mspID string) (*identity.X509Identity, error) { 88 | certificate, err := loadCertificate(certPath) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | id, err := identity.NewX509Identity(mspID, certificate) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return id, nil 99 | } 100 | 101 | // Creates a function that generates a digital signature from a message digest using a private key. 102 | func newSign(keyPath string) (identity.Sign, error) { 103 | privateKeyPEM, err := os.ReadFile(keyPath) 104 | if err != nil { 105 | return nil, errors.Wrap(err, "failed to read private key file") 106 | } 107 | 108 | privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | sign, err := identity.NewPrivateKeySign(privateKey) 114 | if err != nil { 115 | return nil, errors.Wrap(err, "failed to create signer function") 116 | } 117 | 118 | return sign, nil 119 | } 120 | 121 | // Returns error and status code 122 | func ParseError(err error) (error, int) { 123 | var errMsg string 124 | 125 | switch err := err.(type) { 126 | case *client.EndorseError: 127 | errMsg = "endorse error for transaction" 128 | case *client.SubmitError: 129 | errMsg = "submit error for transaction" 130 | case *client.CommitStatusError: 131 | if errors.Is(err, context.DeadlineExceeded) { 132 | errMsg = "timeout waiting for transaction commit status" 133 | } else { 134 | errMsg = "error obtaining commit status for transaction" 135 | } 136 | case *client.CommitError: 137 | errMsg = "transaction failed to commit" 138 | default: 139 | errMsg = "unexpected error type:" + err.Error() 140 | } 141 | 142 | statusErr := status.Convert(err) 143 | 144 | details := statusErr.Details() 145 | if len(details) == 0 { 146 | return errors.New(errMsg), http.StatusInternalServerError 147 | } 148 | 149 | for _, detail := range details { 150 | switch detail := detail.(type) { 151 | case *gateway.ErrorDetail: 152 | status, msg := extractStatusAndMessage(detail.Message) 153 | return errors.New(msg), status 154 | } 155 | } 156 | 157 | return errors.New(errMsg), http.StatusInternalServerError 158 | } 159 | 160 | func extractStatusAndMessage(msg string) (int, string) { 161 | pattern := `chaincode response (\b(\d{3})\b), ` 162 | reg := regexp.MustCompile(pattern) 163 | matches := reg.FindStringSubmatch(msg) 164 | 165 | if len(matches) == 0 { 166 | return http.StatusInternalServerError, msg 167 | } 168 | 169 | errMsg := strings.Replace(msg, matches[0], "", 1) 170 | status, err := strconv.Atoi(matches[1]) 171 | if err != nil { 172 | status = http.StatusInternalServerError 173 | } 174 | 175 | return status, errMsg 176 | } 177 | 178 | func loadCertificate(filename string) (*x509.Certificate, error) { 179 | certificatePEM, err := os.ReadFile(filename) 180 | if err != nil { 181 | return nil, fmt.Errorf("failed to read certificate file: %w", err) 182 | } 183 | return identity.CertificateFromPEM(certificatePEM) 184 | } 185 | 186 | func getSignCert(user string) string { 187 | cryptoPath := GetCryptoPath() 188 | filename := user + "@" + os.Getenv("ORG") + "." + os.Getenv("DOMAIN") + "-cert.pem" 189 | 190 | return strings.Replace(cryptoPath, "{username}", user, 1) + "/signcerts/" + filename 191 | } 192 | 193 | func getSignKey(user string) string { 194 | cryptoPath := GetCryptoPath() 195 | filename := "priv_sk" 196 | 197 | return strings.Replace(cryptoPath, "{username}", user, 1) + "/keystore/" + filename 198 | } 199 | -------------------------------------------------------------------------------- /ccapi/common/resmgnt.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" 5 | "github.com/hyperledger/fabric-sdk-go/pkg/client/ledger" 6 | "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" 7 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 9 | ) 10 | 11 | type fabricResmgtmClient struct { 12 | Provider context.ClientProvider 13 | Client *resmgmt.Client 14 | } 15 | type fabricChannelClient struct { 16 | Provider context.ChannelProvider 17 | Client *channel.Client 18 | } 19 | type fabricLedgerClient struct { 20 | Provider context.ChannelProvider 21 | Client *ledger.Client 22 | } 23 | 24 | // Returns a client which has access resource management capabilities 25 | // These are, but not limited to: create channel, query cfg, cc lifecycle... 26 | // 27 | // Function works like this: 28 | // 1. Get sdk 29 | // 2. Use sdk to create a ClientProvider () 30 | // 3. From client provider create resmgmt Client 31 | // You can then use this .Client to call for specific functionalities 32 | func NewFabricResmgmtClient(orgName, userName string, opts ...resmgmt.ClientOption) (*fabricResmgtmClient, error) { 33 | sdk, err := GetSDK() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | // Create ClientProvider 39 | clientProvider := sdk.CreateClientContext(fabsdk.WithOrg(orgName), fabsdk.WithUser(userName)) 40 | 41 | // Resource management client is responsible for managing channels (create/update channel) 42 | // Supply user that has privileges to create channel 43 | resMgmtClient, err := resmgmt.New(clientProvider, opts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &fabricResmgtmClient{ 49 | Provider: clientProvider, 50 | Client: resMgmtClient, 51 | }, nil 52 | } 53 | 54 | // Returns a client which has channel transaction capabilities 55 | // These are, but not limited to: Execute, Query, Invoke cc... 56 | // 57 | // Function works like this: 58 | // 1. Get sdk 59 | // 2. Use sdk to create a ChannelProvider () 60 | // 3. From channel provider create channel Client 61 | // You can then use this .Client to call for specific functionalities 62 | func NewFabricChClient(channelName, userName, orgName string) (*fabricChannelClient, error) { 63 | sdk, err := GetSDK() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | // Create Channel Provider 69 | chProvider := sdk.CreateChannelContext(channelName, fabsdk.WithUser(userName), fabsdk.WithOrg(orgName)) 70 | 71 | // Create Channel's chClient 72 | chClient, err := channel.New(chProvider) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | return &fabricChannelClient{ 78 | Provider: chProvider, 79 | Client: chClient, 80 | }, nil 81 | } 82 | 83 | // Returns a client which can query a channel's underlying ledger, 84 | // such as QueryBlock and QueryConfig 85 | // 86 | // Function works like this: 87 | // 1. Get sdk 88 | // 2. Use sdk to create a ChannelProvider () 89 | // 3. From channel provider create ledger Client 90 | // You can then use this .Client to call for specific functionalities 91 | func NewFabricLedgerClient(channelName, user, orgName string) (*fabricLedgerClient, error) { 92 | sdk, err := GetSDK() 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | // Create Channel Provider 98 | chProvider := sdk.CreateChannelContext(channelName, fabsdk.WithUser(user), fabsdk.WithOrg(orgName)) 99 | // chProvider := sdk.CreateChannelContext(channelName, ) 100 | // Create Channel's chClient 101 | ledgerClient, err := ledger.New(chProvider) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return &fabricLedgerClient{ 107 | Provider: chProvider, 108 | Client: ledgerClient, 109 | }, nil 110 | } 111 | -------------------------------------------------------------------------------- /ccapi/docker-compose-1org.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | networks: 4 | cc-tools-demo-net: 5 | external: true 6 | 7 | services: 8 | ccapi.org.example.com: 9 | build: 10 | dockerfile: Dockerfile 11 | context: . 12 | ports: 13 | - 80:80 14 | volumes: 15 | - ./:/rest-server 16 | - ../fabric/organizations:/fabric/organizations 17 | logging: 18 | options: 19 | max-size: 50m 20 | environment: 21 | - SDK_PATH=./config/configsdk-org.yaml 22 | - USER=Admin 23 | - ORG=org 24 | - DOMAIN=example.com 25 | - CHANNEL=mainchannel 26 | - CCNAME=cc-tools-demo 27 | - FABRIC_GATEWAY_ENDPOINT=peer0.org.example.com:7051 28 | - FABRIC_GATEWAY_NAME=peer0.org.example.com 29 | - GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn 30 | working_dir: /rest-server 31 | container_name: ccapi.org.example.com 32 | networks: 33 | - cc-tools-demo-net 34 | -------------------------------------------------------------------------------- /ccapi/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | networks: 4 | cc-tools-demo-net: 5 | external: true 6 | 7 | services: 8 | ccapi.org1.example.com: 9 | build: 10 | dockerfile: Dockerfile 11 | context: . 12 | ports: 13 | - 80:80 14 | volumes: 15 | - ./:/rest-server 16 | - ../fabric/organizations:/fabric/organizations 17 | logging: 18 | options: 19 | max-size: 50m 20 | environment: 21 | - SDK_PATH=./config/configsdk-org1.yaml 22 | - USER=Admin 23 | - ORG=org1 24 | - DOMAIN=example.com 25 | - CHANNEL=mainchannel 26 | - CCNAME=cc-tools-demo 27 | - FABRIC_GATEWAY_ENDPOINT=peer0.org1.example.com:7051 28 | - FABRIC_GATEWAY_NAME=peer0.org1.example.com 29 | - GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn 30 | working_dir: /rest-server 31 | container_name: ccapi.org1.example.com 32 | networks: 33 | - cc-tools-demo-net 34 | ccapi.org2.example.com: 35 | build: 36 | dockerfile: Dockerfile 37 | context: . 38 | ports: 39 | - 980:80 40 | volumes: 41 | - ./:/rest-server 42 | - ../fabric/organizations:/fabric/organizations 43 | logging: 44 | options: 45 | max-size: 50m 46 | environment: 47 | - SDK_PATH=./config/configsdk-org2.yaml 48 | - USER=Admin 49 | - ORG=org2 50 | - DOMAIN=example.com 51 | - CHANNEL=mainchannel 52 | - CCNAME=cc-tools-demo 53 | - FABRIC_GATEWAY_ENDPOINT=peer0.org2.example.com:7051 54 | - FABRIC_GATEWAY_NAME=peer0.org2.example.com 55 | - GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn 56 | working_dir: /rest-server 57 | container_name: ccapi.org2.example.com 58 | networks: 59 | - cc-tools-demo-net 60 | ccapi.org3.example.com: 61 | build: 62 | dockerfile: Dockerfile 63 | context: . 64 | ports: 65 | - 1080:80 66 | volumes: 67 | - ./:/rest-server 68 | - ../fabric/organizations:/fabric/organizations 69 | logging: 70 | options: 71 | max-size: 50m 72 | environment: 73 | - SDK_PATH=./config/configsdk-org3.yaml 74 | - USER=Admin 75 | - ORG=org3 76 | - DOMAIN=example.com 77 | - CHANNEL=mainchannel 78 | - CCNAME=cc-tools-demo 79 | - FABRIC_GATEWAY_ENDPOINT=peer0.org3.example.com:7051 80 | - FABRIC_GATEWAY_NAME=peer0.org3.example.com 81 | - GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn 82 | working_dir: /rest-server 83 | container_name: ccapi.org3.example.com 84 | networks: 85 | - cc-tools-demo-net 86 | -------------------------------------------------------------------------------- /ccapi/docs/docs.go: -------------------------------------------------------------------------------- 1 | // Code generated by swaggo/swag. DO NOT EDIT. 2 | 3 | package docs 4 | 5 | import "github.com/swaggo/swag" 6 | 7 | const docTemplate = `{ 8 | "schemes": {{ marshal .Schemes }}, 9 | "swagger": "2.0", 10 | "info": { 11 | "description": "{{escape .Description}}", 12 | "title": "{{.Title}}", 13 | "contact": {}, 14 | "version": "{{.Version}}" 15 | }, 16 | "host": "{{.Host}}", 17 | "basePath": "{{.BasePath}}", 18 | "paths": {} 19 | }` 20 | 21 | // SwaggerInfo holds exported Swagger Info so clients can modify it 22 | var SwaggerInfo = &swag.Spec{ 23 | Version: "", 24 | Host: "", 25 | BasePath: "", 26 | Schemes: []string{}, 27 | Title: "", 28 | Description: "", 29 | InfoInstanceName: "swagger", 30 | SwaggerTemplate: docTemplate, 31 | } 32 | 33 | func init() { 34 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 35 | } 36 | -------------------------------------------------------------------------------- /ccapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger-labs/cc-tools-demo/ccapi 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gin-contrib/cors v1.4.0 7 | github.com/gin-gonic/gin v1.10.0 8 | github.com/hyperledger/fabric-gateway v1.2.2 9 | github.com/hyperledger/fabric-protos-go-apiv2 v0.2.0 10 | github.com/hyperledger/fabric-sdk-go v1.0.0 11 | github.com/pkg/errors v0.9.1 12 | github.com/swaggo/files v1.0.1 13 | github.com/swaggo/gin-swagger v1.6.0 14 | github.com/swaggo/swag v1.8.12 15 | google.golang.org/grpc v1.57.0 16 | google.golang.org/protobuf v1.34.2 17 | ) 18 | 19 | require ( 20 | github.com/Knetic/govaluate v3.0.0+incompatible // indirect 21 | github.com/KyleBanks/depth v1.2.1 // indirect 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/bytedance/sonic v1.11.6 // indirect 24 | github.com/bytedance/sonic/loader v0.1.1 // indirect 25 | github.com/cloudflare/cfssl v1.4.1 // indirect 26 | github.com/cloudwego/base64x v0.1.4 // indirect 27 | github.com/cloudwego/iasm v0.2.0 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/fsnotify/fsnotify v1.4.9 // indirect 30 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 31 | github.com/gin-contrib/sse v0.1.0 // indirect 32 | github.com/go-kit/kit v0.8.0 // indirect 33 | github.com/go-logfmt/logfmt v0.4.0 // indirect 34 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 35 | github.com/go-openapi/jsonreference v0.20.2 // indirect 36 | github.com/go-openapi/spec v0.20.8 // indirect 37 | github.com/go-openapi/swag v0.22.3 // indirect 38 | github.com/go-playground/locales v0.14.1 // indirect 39 | github.com/go-playground/universal-translator v0.18.1 // indirect 40 | github.com/go-playground/validator/v10 v10.20.0 // indirect 41 | github.com/goccy/go-json v0.10.2 // indirect 42 | github.com/golang/mock v1.6.0 // indirect 43 | github.com/golang/protobuf v1.5.3 // indirect 44 | github.com/google/certificate-transparency-go v1.0.21 // indirect 45 | github.com/hashicorp/hcl v1.0.0 // indirect 46 | github.com/hyperledger/fabric-config v0.1.0 // indirect 47 | github.com/hyperledger/fabric-lib-go v1.0.0 // indirect 48 | github.com/hyperledger/fabric-protos-go v0.0.0-20210528200356-82833ecdac31 // indirect 49 | github.com/josharian/intern v1.0.0 // indirect 50 | github.com/json-iterator/go v1.1.12 // indirect 51 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 52 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect 53 | github.com/leodido/go-urn v1.4.0 // indirect 54 | github.com/magiconair/properties v1.8.7 // indirect 55 | github.com/mailru/easyjson v0.7.7 // indirect 56 | github.com/mattn/go-isatty v0.0.20 // indirect 57 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 58 | github.com/miekg/pkcs11 v1.1.1 // indirect 59 | github.com/mitchellh/mapstructure v1.3.2 // indirect 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 61 | github.com/modern-go/reflect2 v1.0.2 // indirect 62 | github.com/pelletier/go-toml v1.8.0 // indirect 63 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 64 | github.com/pmezard/go-difflib v1.0.0 // indirect 65 | github.com/prometheus/client_golang v1.1.0 // indirect 66 | github.com/prometheus/client_model v0.3.0 // indirect 67 | github.com/prometheus/common v0.6.0 // indirect 68 | github.com/prometheus/procfs v0.0.3 // indirect 69 | github.com/spf13/afero v1.9.2 // indirect 70 | github.com/spf13/cast v1.3.1 // indirect 71 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 72 | github.com/spf13/pflag v1.0.5 // indirect 73 | github.com/spf13/viper v1.7.1 // indirect 74 | github.com/stretchr/testify v1.9.0 // indirect 75 | github.com/subosito/gotenv v1.6.0 // indirect 76 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 77 | github.com/ugorji/go/codec v1.2.12 // indirect 78 | github.com/weppos/publicsuffix-go v0.5.0 // indirect 79 | github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e // indirect 80 | github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb // indirect 81 | golang.org/x/arch v0.8.0 // indirect 82 | golang.org/x/crypto v0.23.0 // indirect 83 | golang.org/x/net v0.25.0 // indirect 84 | golang.org/x/sys v0.20.0 // indirect 85 | golang.org/x/text v0.15.0 // indirect 86 | golang.org/x/tools v0.7.0 // indirect 87 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect 88 | gopkg.in/ini.v1 v1.67.0 // indirect 89 | gopkg.in/yaml.v2 v2.4.0 // indirect 90 | gopkg.in/yaml.v3 v3.0.1 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /ccapi/handlers/invoke.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 12 | ) 13 | 14 | func Invoke(c *gin.Context) { 15 | // Get channel information from request 16 | req := make(map[string]interface{}) 17 | err := c.BindJSON(&req) 18 | if err != nil { 19 | common.Abort(c, http.StatusBadRequest, err) 20 | return 21 | } 22 | channelName := c.Param("channelName") 23 | chaincodeName := c.Param("chaincodeName") 24 | txName := c.Param("txname") 25 | 26 | var collections []string 27 | collectionsQuery := c.Query("@collections") 28 | if collectionsQuery != "" { 29 | collectionsByte, err := base64.StdEncoding.DecodeString(collectionsQuery) 30 | if err != nil { 31 | c.JSON(http.StatusBadRequest, gin.H{ 32 | "error": "the @collections query parameter must be a base64-encoded JSON array of strings", 33 | }) 34 | return 35 | } 36 | 37 | err = json.Unmarshal(collectionsByte, &collections) 38 | if err != nil { 39 | c.JSON(http.StatusBadRequest, gin.H{ 40 | "error": "the @collections query parameter must be a base64-encoded JSON array of strings", 41 | }) 42 | return 43 | } 44 | } else { 45 | collectionsQuery := c.QueryArray("collections") 46 | if len(collectionsQuery) > 0 { 47 | collections = collectionsQuery 48 | } else { 49 | collections = []string{c.Query("collections")} 50 | } 51 | } 52 | 53 | transientMap := make(map[string]interface{}) 54 | for key, value := range req { 55 | if key[0] == '~' { 56 | keyTrimmed := strings.TrimPrefix(key, "~") 57 | transientMap[keyTrimmed] = value 58 | delete(req, key) 59 | } 60 | } 61 | 62 | args, err := json.Marshal(req) 63 | if err != nil { 64 | common.Abort(c, http.StatusInternalServerError, err) 65 | return 66 | } 67 | 68 | transientMapByte, err := json.Marshal(transientMap) 69 | if err != nil { 70 | c.JSON(http.StatusInternalServerError, gin.H{ 71 | "error": err.Error(), 72 | }) 73 | return 74 | } 75 | 76 | argList := [][]byte{} 77 | if args != nil { 78 | argList = append(argList, args) 79 | } 80 | 81 | user := c.GetHeader("User") 82 | if user == "" { 83 | user = "Admin" 84 | } 85 | 86 | res, status, err := chaincode.Invoke(channelName, chaincodeName, txName, user, argList, transientMapByte) 87 | if err != nil { 88 | common.Abort(c, status, err) 89 | return 90 | } 91 | 92 | var payload interface{} 93 | err = json.Unmarshal(res.Payload, &payload) 94 | if err != nil { 95 | common.Abort(c, http.StatusInternalServerError, err) 96 | return 97 | } 98 | 99 | common.Respond(c, payload, status, err) 100 | } 101 | -------------------------------------------------------------------------------- /ccapi/handlers/invokeGateway.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 12 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | func InvokeGatewayDefault(c *gin.Context) { 17 | channelName := os.Getenv("CHANNEL") 18 | chaincodeName := os.Getenv("CCNAME") 19 | 20 | invokeGateway(c, channelName, chaincodeName) 21 | } 22 | 23 | func InvokeGatewayCustom(c *gin.Context) { 24 | channelName := c.Param("channelName") 25 | chaincodeName := c.Param("chaincodeName") 26 | 27 | invokeGateway(c, channelName, chaincodeName) 28 | } 29 | 30 | func invokeGateway(c *gin.Context, channelName, chaincodeName string) { 31 | // Get request body 32 | req := make(map[string]interface{}) 33 | err := c.BindJSON(&req) 34 | if err != nil { 35 | common.Abort(c, http.StatusBadRequest, err) 36 | return 37 | } 38 | 39 | txName := c.Param("txname") 40 | 41 | // Get endorsers names 42 | var endorsers []string 43 | endorsersQuery := c.Query("@endorsers") 44 | if endorsersQuery != "" { 45 | endorsersByte, err := base64.StdEncoding.DecodeString(endorsersQuery) 46 | if err != nil { 47 | c.JSON(http.StatusBadRequest, gin.H{ 48 | "error": "the @endorsers query parameter must be a base64-encoded JSON array of strings", 49 | }) 50 | return 51 | } 52 | 53 | err = json.Unmarshal(endorsersByte, &endorsers) 54 | if err != nil { 55 | c.JSON(http.StatusBadRequest, gin.H{ 56 | "error": "the @endorsers query parameter must be a base64-encoded JSON array of strings", 57 | }) 58 | return 59 | } 60 | } 61 | 62 | // Make transient request 63 | transientMap := make(map[string]interface{}) 64 | for key, value := range req { 65 | if key[0] == '~' { 66 | keyTrimmed := strings.TrimPrefix(key, "~") 67 | transientMap[keyTrimmed] = value 68 | delete(req, key) 69 | } 70 | } 71 | 72 | transientBytes, _ := json.Marshal(transientMap) 73 | if len(transientMap) == 0 { 74 | transientMap = nil 75 | } 76 | 77 | // Make args 78 | reqBytes, err := json.Marshal(req) 79 | if err != nil { 80 | common.Abort(c, http.StatusInternalServerError, errors.Wrap(err, "failed to marshal req body")) 81 | return 82 | } 83 | 84 | // Invoke 85 | user := c.GetHeader("User") 86 | if user == "" { 87 | user = "Admin" 88 | } 89 | 90 | result, err := chaincode.InvokeGateway(channelName, chaincodeName, txName, user, []string{string(reqBytes)}, transientBytes, endorsers) 91 | if err != nil { 92 | err, status := common.ParseError(err) 93 | common.Abort(c, status, err) 94 | return 95 | } 96 | 97 | // Parse response 98 | var payload interface{} 99 | err = json.Unmarshal(result, &payload) 100 | if err != nil { 101 | common.Abort(c, http.StatusInternalServerError, err) 102 | return 103 | } 104 | 105 | common.Respond(c, payload, http.StatusOK, nil) 106 | } 107 | -------------------------------------------------------------------------------- /ccapi/handlers/invokeV1.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 12 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 13 | ) 14 | 15 | func InvokeV1(c *gin.Context) { 16 | // Get channel information from request 17 | req := make(map[string]interface{}) 18 | err := c.BindJSON(&req) 19 | if err != nil { 20 | common.Abort(c, http.StatusBadRequest, err) 21 | return 22 | } 23 | 24 | channelName := os.Getenv("CHANNEL") 25 | chaincodeName := os.Getenv("CCNAME") 26 | txName := c.Param("txname") 27 | 28 | var collections []string 29 | collectionsQuery := c.Query("@collections") 30 | if collectionsQuery != "" { 31 | collectionsByte, err := base64.StdEncoding.DecodeString(collectionsQuery) 32 | if err != nil { 33 | c.JSON(http.StatusBadRequest, gin.H{ 34 | "error": "the @collections query parameter must be a base64-encoded JSON array of strings", 35 | }) 36 | return 37 | } 38 | 39 | err = json.Unmarshal(collectionsByte, &collections) 40 | if err != nil { 41 | c.JSON(http.StatusBadRequest, gin.H{ 42 | "error": "the @collections query parameter must be a base64-encoded JSON array of strings", 43 | }) 44 | return 45 | } 46 | } else { 47 | collectionsQuery := c.QueryArray("collections") 48 | if len(collectionsQuery) > 0 { 49 | collections = collectionsQuery 50 | } else { 51 | collections = []string{c.Query("collections")} 52 | } 53 | } 54 | 55 | transientMap := make(map[string]interface{}) 56 | for key, value := range req { 57 | if key[0] == '~' { 58 | keyTrimmed := strings.TrimPrefix(key, "~") 59 | transientMap[keyTrimmed] = value 60 | delete(req, key) 61 | } 62 | } 63 | 64 | args, err := json.Marshal(req) 65 | if err != nil { 66 | common.Abort(c, http.StatusInternalServerError, err) 67 | return 68 | } 69 | 70 | transientMapByte, err := json.Marshal(transientMap) 71 | if err != nil { 72 | c.JSON(http.StatusInternalServerError, gin.H{ 73 | "error": err.Error(), 74 | }) 75 | return 76 | } 77 | 78 | argList := [][]byte{} 79 | if args != nil { 80 | argList = append(argList, args) 81 | } 82 | 83 | user := c.GetHeader("User") 84 | if user == "" { 85 | user = "Admin" 86 | } 87 | 88 | res, status, err := chaincode.Invoke(channelName, chaincodeName, txName, user, argList, transientMapByte) 89 | if err != nil { 90 | common.Abort(c, status, err) 91 | return 92 | } 93 | 94 | var payload interface{} 95 | err = json.Unmarshal(res.Payload, &payload) 96 | if err != nil { 97 | common.Abort(c, http.StatusInternalServerError, err) 98 | return 99 | } 100 | 101 | common.Respond(c, payload, status, err) 102 | } 103 | -------------------------------------------------------------------------------- /ccapi/handlers/query.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 10 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 11 | ) 12 | 13 | func Query(c *gin.Context) { 14 | var args []byte 15 | var err error 16 | 17 | if c.Request.Method == "GET" { 18 | request := c.Query("@request") 19 | if request != "" { 20 | args, _ = base64.StdEncoding.DecodeString(request) 21 | } 22 | } else if c.Request.Method == "POST" { 23 | req := make(map[string]interface{}) 24 | c.ShouldBind(&req) 25 | args, err = json.Marshal(req) 26 | if err != nil { 27 | common.Abort(c, http.StatusInternalServerError, err) 28 | return 29 | } 30 | } 31 | 32 | channelName := c.Param("channelName") 33 | chaincodeName := c.Param("chaincodeName") 34 | txName := c.Param("txname") 35 | 36 | argList := [][]byte{} 37 | if args != nil { 38 | argList = append(argList, args) 39 | } 40 | 41 | user := c.GetHeader("User") 42 | if user == "" { 43 | user = "Admin" 44 | } 45 | 46 | res, status, err := chaincode.Query(channelName, chaincodeName, txName, user, argList) 47 | if err != nil { 48 | common.Abort(c, status, err) 49 | return 50 | } 51 | 52 | var payload interface{} 53 | err = json.Unmarshal(res.Payload, &payload) 54 | if err != nil { 55 | common.Abort(c, http.StatusInternalServerError, err) 56 | return 57 | } 58 | 59 | common.Respond(c, payload, status, err) 60 | } 61 | -------------------------------------------------------------------------------- /ccapi/handlers/queryGateway.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 12 | ) 13 | 14 | func QueryGatewayDefault(c *gin.Context) { 15 | channelName := os.Getenv("CHANNEL") 16 | chaincodeName := os.Getenv("CCNAME") 17 | 18 | queryGateway(c, channelName, chaincodeName) 19 | } 20 | 21 | func QueryGatewayCustom(c *gin.Context) { 22 | channelName := c.Param("channelName") 23 | chaincodeName := c.Param("chaincodeName") 24 | 25 | queryGateway(c, channelName, chaincodeName) 26 | } 27 | 28 | func queryGateway(c *gin.Context, channelName, chaincodeName string) { 29 | var args []byte 30 | var err error 31 | 32 | // Get request data 33 | if c.Request.Method == "GET" { 34 | request := c.Query("@request") 35 | if request != "" { 36 | args, _ = base64.StdEncoding.DecodeString(request) 37 | } 38 | } else if c.Request.Method == "POST" { 39 | req := make(map[string]interface{}) 40 | c.ShouldBind(&req) 41 | args, err = json.Marshal(req) 42 | if err != nil { 43 | common.Abort(c, http.StatusInternalServerError, err) 44 | return 45 | } 46 | } 47 | 48 | txName := c.Param("txname") 49 | 50 | // Query 51 | user := c.GetHeader("User") 52 | if user == "" { 53 | user = "Admin" 54 | } 55 | 56 | result, err := chaincode.QueryGateway(channelName, chaincodeName, txName, user, []string{string(args)}) 57 | if err != nil { 58 | err, status := common.ParseError(err) 59 | common.Abort(c, status, err) 60 | return 61 | } 62 | 63 | // Parse response 64 | var payload interface{} 65 | err = json.Unmarshal(result, &payload) 66 | if err != nil { 67 | common.Abort(c, http.StatusInternalServerError, err) 68 | return 69 | } 70 | 71 | common.Respond(c, payload, http.StatusOK, nil) 72 | } 73 | -------------------------------------------------------------------------------- /ccapi/handlers/queryV1.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 12 | ) 13 | 14 | func QueryV1(c *gin.Context) { 15 | var args []byte 16 | var err error 17 | 18 | if c.Request.Method == "GET" { 19 | request := c.Query("@request") 20 | if request != "" { 21 | args, _ = base64.StdEncoding.DecodeString(request) 22 | } 23 | } else if c.Request.Method == "POST" { 24 | req := make(map[string]interface{}) 25 | c.ShouldBind(&req) 26 | args, err = json.Marshal(req) 27 | if err != nil { 28 | common.Abort(c, http.StatusInternalServerError, err) 29 | return 30 | } 31 | } 32 | 33 | channelName := os.Getenv("CHANNEL") 34 | chaincodeName := os.Getenv("CCNAME") 35 | txName := c.Param("txname") 36 | 37 | argList := [][]byte{} 38 | if args != nil { 39 | argList = append(argList, args) 40 | } 41 | 42 | user := c.GetHeader("User") 43 | if user == "" { 44 | user = "Admin" 45 | } 46 | 47 | res, status, err := chaincode.Query(channelName, chaincodeName, txName, user, argList) 48 | if err != nil { 49 | common.Abort(c, status, err) 50 | return 51 | } 52 | 53 | var payload interface{} 54 | err = json.Unmarshal(res.Payload, &payload) 55 | if err != nil { 56 | common.Abort(c, http.StatusInternalServerError, err) 57 | return 58 | } 59 | 60 | common.Respond(c, payload, status, err) 61 | } 62 | -------------------------------------------------------------------------------- /ccapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "os/signal" 8 | 9 | "github.com/gin-contrib/cors" 10 | "github.com/gin-gonic/gin" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/chaincode" 12 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/server" 13 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 14 | ) 15 | 16 | func main() { 17 | ctx, cancel := context.WithCancel(context.Background()) 18 | 19 | // Create gin handler and start server 20 | r := gin.Default() 21 | r.Use(cors.New(cors.Config{ 22 | AllowOrigins: []string{ 23 | "http://localhost:8080", // Test addresses 24 | "*", 25 | }, 26 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, 27 | AllowHeaders: []string{"Authorization", "Origin", "Content-Type"}, 28 | AllowCredentials: true, 29 | })) 30 | go server.Serve(r, ctx) 31 | 32 | // Register to chaincode events 33 | go chaincode.WaitForEvent(os.Getenv("CHANNEL"), os.Getenv("CCNAME"), "eventName", func(ccEvent *fab.CCEvent) { 34 | log.Println("Received CC event: ", ccEvent) 35 | }) 36 | 37 | chaincode.RegisterForEvents() 38 | 39 | quit := make(chan os.Signal, 1) 40 | signal.Notify(quit, os.Interrupt) 41 | 42 | <-quit 43 | cancel() 44 | } 45 | -------------------------------------------------------------------------------- /ccapi/routes/chaincode.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/handlers" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func addCCRoutes(rg *gin.RouterGroup) { 10 | // Gateway routes 11 | rg.POST("/gateway/:channelName/:chaincodeName/invoke/:txname", handlers.InvokeGatewayCustom) 12 | rg.PUT("/gateway/:channelName/:chaincodeName/invoke/:txname", handlers.InvokeGatewayCustom) 13 | rg.DELETE("/gateway/:channelName/:chaincodeName/invoke/:txname", handlers.InvokeGatewayCustom) 14 | rg.POST("/gateway/:channelName/:chaincodeName/query/:txname", handlers.QueryGatewayCustom) 15 | rg.GET("/gateway/:channelName/:chaincodeName/query/:txname", handlers.QueryGatewayCustom) 16 | 17 | rg.POST("/gateway/invoke/:txname", handlers.InvokeGatewayDefault) 18 | rg.PUT("/gateway/invoke/:txname", handlers.InvokeGatewayDefault) 19 | rg.DELETE("/gateway/invoke/:txname", handlers.InvokeGatewayDefault) 20 | rg.POST("/gateway/query/:txname", handlers.QueryGatewayDefault) 21 | rg.GET("/gateway/query/:txname", handlers.QueryGatewayDefault) 22 | 23 | // Other 24 | rg.POST("/:channelName/:chaincodeName/invoke/:txname", handlers.Invoke) 25 | rg.PUT("/:channelName/:chaincodeName/invoke/:txname", handlers.Invoke) 26 | rg.DELETE("/:channelName/:chaincodeName/invoke/:txname", handlers.Invoke) 27 | rg.POST("/:channelName/:chaincodeName/query/:txname", handlers.Query) 28 | rg.GET("/:channelName/:chaincodeName/query/:txname", handlers.Query) 29 | 30 | rg.POST("/invoke/:txname/", handlers.InvokeV1) 31 | rg.POST("/invoke/:txname", handlers.InvokeV1) 32 | rg.PUT("/invoke/:txname/", handlers.InvokeV1) 33 | rg.PUT("/invoke/:txname", handlers.InvokeV1) 34 | rg.DELETE("/invoke/:txname/", handlers.InvokeV1) 35 | rg.DELETE("/invoke/:txname", handlers.InvokeV1) 36 | rg.POST("/query/:txname/", handlers.QueryV1) 37 | rg.POST("/query/:txname", handlers.QueryV1) 38 | rg.GET("/query/:txname/", handlers.QueryV1) 39 | rg.GET("/query/:txname", handlers.QueryV1) 40 | 41 | rg.GET("/:channelName/qscc/:txname", handlers.QueryQSCC) 42 | } 43 | -------------------------------------------------------------------------------- /ccapi/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/docs" 6 | swaggerfiles "github.com/swaggo/files" 7 | ginSwagger "github.com/swaggo/gin-swagger" 8 | ) 9 | 10 | // Register routes and handlers used by engine 11 | func AddRoutesToEngine(r *gin.Engine) { 12 | r.GET("/", func(c *gin.Context) { 13 | c.Redirect(301, "/api-docs/index.html") 14 | }) 15 | 16 | r.GET("/ping", func(c *gin.Context) { 17 | c.JSON(200, gin.H{ 18 | "status": "ok", 19 | }) 20 | }) 21 | 22 | // serve swagger files 23 | docs.SwaggerInfo.BasePath = "/api" 24 | r.StaticFile("/swagger.yaml", "./docs/swagger.yaml") 25 | 26 | url := ginSwagger.URL("/swagger.yaml") 27 | r.GET("/api-docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler, url)) 28 | 29 | // CHANNEL routes 30 | chaincodeRG := r.Group("/api") 31 | addCCRoutes(chaincodeRG) 32 | 33 | // Update SDK route 34 | sdkRG := r.Group("/sdk") 35 | addSDKRoutes(sdkRG) 36 | } 37 | -------------------------------------------------------------------------------- /ccapi/routes/sdk.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func addSDKRoutes(rg *gin.RouterGroup) { 8 | // Update SDK route 9 | // rg.POST("/update", handlers.UpdateSDK) 10 | } 11 | -------------------------------------------------------------------------------- /ccapi/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/common" 12 | "github.com/hyperledger-labs/cc-tools-demo/ccapi/routes" 13 | ) 14 | 15 | func defaultServer(r *gin.Engine) *http.Server { 16 | return &http.Server{ 17 | Addr: ":80", 18 | Handler: r, 19 | } 20 | } 21 | 22 | // Serve starts the server with gin's default engine. 23 | // Server gracefully shut's down 24 | func Serve(r *gin.Engine, ctx context.Context) { 25 | // Defer close sdk to clear cache and free memory 26 | defer common.CloseSDK() 27 | 28 | // Register routes and handlers 29 | routes.AddRoutesToEngine(r) 30 | 31 | // Returns a http.Server from provided handler 32 | srv := defaultServer(r) 33 | 34 | // listen and serve on 0.0.0.0:80 (for windows "localhost:80") 35 | go func(server *http.Server) { 36 | log.Println("Listening on port 80") 37 | err := srv.ListenAndServe() 38 | if err != http.ErrServerClosed { 39 | log.Panic(err) 40 | } 41 | }(srv) 42 | 43 | // Graceful shutdown 44 | <-ctx.Done() 45 | 46 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 47 | defer cancel() 48 | 49 | if err := srv.Shutdown(ctx); err != nil { 50 | log.Panic(err) 51 | } 52 | log.Println("Shutting down") 53 | } 54 | 55 | // Serve sync starts the server with a given wait group. 56 | // When server starts, the wait group counter is increased and processes 57 | // that depend on server can be ran synchronously with it 58 | func ServeSync(ctx context.Context, wg *sync.WaitGroup) { 59 | gin.SetMode(gin.TestMode) 60 | r := gin.New() 61 | 62 | routes.AddRoutesToEngine(r) 63 | 64 | srv := defaultServer(r) 65 | 66 | go func(server *http.Server) { 67 | log.Println("Listening on port 80") 68 | err := srv.ListenAndServe() 69 | if err != http.ErrServerClosed { 70 | log.Panic(err) 71 | } 72 | // finish wait group 73 | time.Sleep(1 * time.Second) 74 | wg.Done() 75 | }(srv) 76 | 77 | wg.Add(1) 78 | <-ctx.Done() 79 | 80 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 81 | defer cancel() 82 | 83 | if err := srv.Shutdown(ctx); err != nil { 84 | log.Panic(err) 85 | } 86 | log.Println("Shutting down") 87 | } 88 | -------------------------------------------------------------------------------- /ccapi/web-client/docker-compose-goinitus.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | networks: 4 | cc-tools-demo-net: 5 | external: true 6 | 7 | services: 8 | goinitus: 9 | image: goledger/cc-webclient:latest 10 | container_name: goinitus 11 | ports: 12 | - "8080:80" 13 | networks: 14 | - cc-tools-demo-net 15 | -------------------------------------------------------------------------------- /chaincode/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | ARG GO_VER=1.21 6 | ARG ALPINE_VER=3.20 7 | ARG CC_SERVER_PORT=9999 8 | 9 | FROM golang:${GO_VER}-alpine${ALPINE_VER} 10 | 11 | WORKDIR /go/src/github.com/hyperledger-labs/cc-tools-demo 12 | COPY . . 13 | 14 | RUN go get -d -v . 15 | RUN go build -o cc-tools-demo -v . 16 | 17 | EXPOSE ${CC_SERVER_PORT} 18 | CMD ["./cc-tools-demo"] 19 | -------------------------------------------------------------------------------- /chaincode/META-INF/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/cc-tools-demo/f314edfda1b65d6d6cefd0e53d7da717f7d98c92/chaincode/META-INF/.gitkeep -------------------------------------------------------------------------------- /chaincode/META-INF/statedb/couchdb/indexes/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "index":{ 3 | "fields":[ 4 | {"@assetType": "asc"} 5 | ] 6 | }, 7 | "ddoc":"indexAssetTypeDoc", 8 | "name":"indexAssetType", 9 | "type":"json" 10 | } -------------------------------------------------------------------------------- /chaincode/assetTypeList.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools-demo/chaincode/assettypes" 5 | "github.com/hyperledger-labs/cc-tools/assets" 6 | ) 7 | 8 | var assetTypeList = []assets.AssetType{ 9 | assettypes.Person, 10 | assettypes.Book, 11 | assettypes.Library, 12 | assettypes.Secret, 13 | } 14 | -------------------------------------------------------------------------------- /chaincode/assettypes/book.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import "github.com/hyperledger-labs/cc-tools/assets" 4 | 5 | // Description of a book 6 | var Book = assets.AssetType{ 7 | Tag: "book", 8 | Label: "Book", 9 | Description: "Book", 10 | 11 | Props: []assets.AssetProp{ 12 | { 13 | // Composite Key 14 | Required: true, 15 | IsKey: true, 16 | Tag: "title", 17 | Label: "Book Title", 18 | DataType: "string", 19 | Writers: []string{`org2MSP`, "orgMSP"}, // This means only org2 can create the asset (others can edit) 20 | }, 21 | { 22 | // Composite Key 23 | Required: true, 24 | IsKey: true, 25 | Tag: "author", 26 | Label: "Book Author", 27 | DataType: "string", 28 | Writers: []string{`org2MSP`, "orgMSP"}, // This means only org2 can create the asset (others can edit) 29 | }, 30 | { 31 | /// Reference to another asset 32 | Tag: "currentTenant", 33 | Label: "Current Tenant", 34 | DataType: "->person", 35 | }, 36 | { 37 | // String list 38 | Tag: "genres", 39 | Label: "Genres", 40 | DataType: "[]string", 41 | }, 42 | { 43 | // Date property 44 | Tag: "published", 45 | Label: "Publishment Date", 46 | DataType: "datetime", 47 | }, 48 | { 49 | // Custom data type 50 | Tag: "bookType", 51 | Label: "Book Type", 52 | DataType: "bookType", 53 | }, 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /chaincode/assettypes/customAssets.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools/assets" 5 | ) 6 | 7 | // CustomAssets contains all assets inserted via GoFabric's Template mode. 8 | // For local development, this can be empty or could contain assets that 9 | // are supposed to be defined via the Template mode. 10 | var CustomAssets = []assets.AssetType{} 11 | -------------------------------------------------------------------------------- /chaincode/assettypes/dynamicAssetTypes.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools/assets" 5 | ) 6 | 7 | // DynamicAssetTypes contains the configuration for the Dynamic AssetTypes feature. 8 | var DynamicAssetTypes = assets.DynamicAssetType{ 9 | Enabled: true, 10 | AssetAdmins: []string{`org1MSP`, "orgMSP"}, 11 | } 12 | -------------------------------------------------------------------------------- /chaincode/assettypes/library.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import "github.com/hyperledger-labs/cc-tools/assets" 4 | 5 | // Description of a Library as a collection of books 6 | var Library = assets.AssetType{ 7 | Tag: "library", 8 | Label: "Library", 9 | Description: "Library as a collection of books", 10 | 11 | Props: []assets.AssetProp{ 12 | { 13 | // Primary Key 14 | Required: true, 15 | IsKey: true, 16 | Tag: "name", 17 | Label: "Library Name", 18 | DataType: "string", 19 | Writers: []string{`org3MSP`, "orgMSP"}, // This means only org3 can create the asset (others can edit) 20 | }, 21 | { 22 | // Asset reference list 23 | Tag: "books", 24 | Label: "Book Collection", 25 | DataType: "[]->book", 26 | }, 27 | { 28 | // Asset reference list 29 | Tag: "entranceCode", 30 | Label: "Entrance Code for the Library", 31 | DataType: "->secret", 32 | }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /chaincode/assettypes/person.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hyperledger-labs/cc-tools/assets" 7 | ) 8 | 9 | var Person = assets.AssetType{ 10 | Tag: "person", 11 | Label: "Person", 12 | Description: "Personal data of someone", 13 | 14 | Props: []assets.AssetProp{ 15 | { 16 | // Primary key 17 | Required: true, 18 | IsKey: true, 19 | Tag: "id", 20 | Label: "CPF (Brazilian ID)", 21 | DataType: "cpf", // Datatypes are identified at datatypes folder 22 | Writers: []string{`org1MSP`, "orgMSP"}, // This means only org1 can create the asset (others can edit) 23 | }, 24 | { 25 | // Mandatory property 26 | Required: true, 27 | Tag: "name", 28 | Label: "Name of the person", 29 | DataType: "string", 30 | // Validate funcion 31 | Validate: func(name interface{}) error { 32 | nameStr := name.(string) 33 | if nameStr == "" { 34 | return fmt.Errorf("name must be non-empty") 35 | } 36 | return nil 37 | }, 38 | }, 39 | { 40 | // Optional property 41 | Tag: "dateOfBirth", 42 | Label: "Date of Birth", 43 | DataType: "datetime", 44 | }, 45 | { 46 | // Property with default value 47 | Tag: "height", 48 | Label: "Person's height", 49 | DefaultValue: 0, 50 | DataType: "number", 51 | }, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /chaincode/assettypes/secret.go: -------------------------------------------------------------------------------- 1 | package assettypes 2 | 3 | import "github.com/hyperledger-labs/cc-tools/assets" 4 | 5 | // Secret is and information available only for org2 and org3 6 | // Collections.json configuration is necessary 7 | var Secret = assets.AssetType{ 8 | Tag: "secret", 9 | Label: "Secret", 10 | Description: "Secret between Org2 and Org3", 11 | 12 | Readers: []string{"org2MSP", "org3MSP", "orgMSP"}, 13 | Props: []assets.AssetProp{ 14 | { 15 | // Primary Key 16 | IsKey: true, 17 | Tag: "secretName", 18 | Label: "Secret Name", 19 | DataType: "string", 20 | Writers: []string{`org2MSP`, "orgMSP"}, // This means only org2 can create the asset (org3 can edit) 21 | }, 22 | { 23 | // Mandatory Property 24 | Required: true, 25 | Tag: "secret", 26 | Label: "Secret", 27 | DataType: "string", 28 | }, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /chaincode/datatypes/bookType.go: -------------------------------------------------------------------------------- 1 | package datatypes 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/hyperledger-labs/cc-tools/assets" 8 | "github.com/hyperledger-labs/cc-tools/errors" 9 | ) 10 | 11 | // Example of a custom data type using enum-like structure (iota) 12 | // This allows the use of verification by const values instead of float64, improving readability 13 | // Example: 14 | // if assetMap["bookType"].(float64) == (float64)(BookTypeHardcover) 15 | // ... 16 | 17 | type BookType float64 18 | 19 | const ( 20 | BookTypeHardcover BookType = iota 21 | BookTypePaperback 22 | BookTypeEbook 23 | ) 24 | 25 | // CheckType checks if the given value is defined as valid BookType consts 26 | func (b BookType) CheckType() errors.ICCError { 27 | switch b { 28 | case BookTypeHardcover: 29 | return nil 30 | case BookTypePaperback: 31 | return nil 32 | case BookTypeEbook: 33 | return nil 34 | default: 35 | return errors.NewCCError("invalid type", 400) 36 | } 37 | 38 | } 39 | 40 | var bookType = assets.DataType{ 41 | AcceptedFormats: []string{"number"}, 42 | DropDownValues: map[string]interface{}{ 43 | "Hardcover": BookTypeHardcover, 44 | "Paperback": BookTypePaperback, 45 | "Ebook": BookTypeEbook, 46 | }, 47 | Description: ``, 48 | 49 | Parse: func(data interface{}) (string, interface{}, errors.ICCError) { 50 | var dataVal float64 51 | switch v := data.(type) { 52 | case float64: 53 | dataVal = v 54 | case int: 55 | dataVal = (float64)(v) 56 | case BookType: 57 | dataVal = (float64)(v) 58 | case string: 59 | var err error 60 | dataVal, err = strconv.ParseFloat(v, 64) 61 | if err != nil { 62 | return "", nil, errors.WrapErrorWithStatus(err, "asset property must be an integer, is %t", 400) 63 | } 64 | default: 65 | return "", nil, errors.NewCCError("asset property must be an integer, is %t", 400) 66 | } 67 | 68 | retVal := (BookType)(dataVal) 69 | err := retVal.CheckType() 70 | return fmt.Sprint(retVal), retVal, err 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /chaincode/datatypes/cpf.go: -------------------------------------------------------------------------------- 1 | package datatypes 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/hyperledger-labs/cc-tools/assets" 7 | "github.com/hyperledger-labs/cc-tools/errors" 8 | ) 9 | 10 | var cpf = assets.DataType{ 11 | AcceptedFormats: []string{"string"}, 12 | Parse: func(data interface{}) (string, interface{}, errors.ICCError) { 13 | cpf, ok := data.(string) 14 | if !ok { 15 | return "", nil, errors.NewCCError("property must be a string", 400) 16 | } 17 | 18 | cpf = strings.ReplaceAll(cpf, ".", "") 19 | cpf = strings.ReplaceAll(cpf, "-", "") 20 | 21 | if len(cpf) != 11 { 22 | return "", nil, errors.NewCCError("CPF must have 11 digits", 400) 23 | } 24 | 25 | var vd0 int 26 | for i, d := range cpf { 27 | if i >= 9 { 28 | break 29 | } 30 | dnum := int(d) - '0' 31 | vd0 += (10 - i) * dnum 32 | } 33 | vd0 = 11 - vd0%11 34 | if vd0 > 9 { 35 | vd0 = 0 36 | } 37 | if int(cpf[9])-'0' != vd0 { 38 | return "", nil, errors.NewCCError("Invalid CPF", 400) 39 | } 40 | 41 | var vd1 int 42 | for i, d := range cpf { 43 | if i >= 10 { 44 | break 45 | } 46 | dnum := int(d) - '0' 47 | vd1 += (11 - i) * dnum 48 | } 49 | vd1 = 11 - vd1%11 50 | if vd1 > 9 { 51 | vd1 = 0 52 | } 53 | if int(cpf[10])-'0' != vd1 { 54 | return "", nil, errors.NewCCError("Invalid CPF", 400) 55 | } 56 | 57 | return cpf, cpf, nil 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /chaincode/datatypes/datatypes.go: -------------------------------------------------------------------------------- 1 | package datatypes 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools/assets" 5 | ) 6 | 7 | // CustomDataTypes contain the user-defined primary data types 8 | var CustomDataTypes = map[string]assets.DataType{ 9 | "cpf": cpf, 10 | "bookType": bookType, 11 | } 12 | -------------------------------------------------------------------------------- /chaincode/eventTypeList.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hyperledger-labs/cc-tools-demo/chaincode/eventtypes" 5 | "github.com/hyperledger-labs/cc-tools/events" 6 | ) 7 | 8 | var eventTypeList = []events.Event{ 9 | eventtypes.CreateLibraryLog, 10 | } 11 | -------------------------------------------------------------------------------- /chaincode/eventtypes/createLibraryLog.go: -------------------------------------------------------------------------------- 1 | package eventtypes 2 | 3 | import "github.com/hyperledger-labs/cc-tools/events" 4 | 5 | // CreateLibraryLog is a log to be emitted on the CCAPI when a library is created 6 | var CreateLibraryLog = events.Event{ 7 | Tag: "createLibraryLog", 8 | Label: "Create Library Log", 9 | Description: "Log of a library creation", 10 | Type: events.EventLog, // Event funciton is to log on the CCAPI 11 | BaseLog: "New library created", // BaseLog is a base message to be logged 12 | Receivers: []string{"$org2MSP", "$orgMSP"}, // Receivers are the MSPs that will receive the event 13 | } 14 | -------------------------------------------------------------------------------- /chaincode/generateCollections.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type ArrayFlags []string 10 | 11 | func (i *ArrayFlags) String() string { 12 | return "my string representation" 13 | } 14 | 15 | func (i *ArrayFlags) Set(value string) error { 16 | *i = append(*i, value) 17 | return nil 18 | } 19 | 20 | type CollectionElem struct { 21 | Name string `json:"name"` 22 | RequiredPeerCount int `json:"requiredPeerCount"` 23 | MaxPeerCount int `json:"maxPeerCount"` 24 | BlockToLive int `json:"blockToLive"` 25 | MemberOnlyRead bool `json:"memberOnlyRead"` 26 | Policy string `json:"policy"` 27 | } 28 | 29 | func generateCollection(orgs ArrayFlags) { 30 | collection := []CollectionElem{} 31 | 32 | for _, a := range assetTypeList { 33 | if len(a.Readers) > 0 { 34 | elem := CollectionElem{ 35 | Name: a.Tag, 36 | RequiredPeerCount: 0, 37 | MaxPeerCount: 3, 38 | BlockToLive: 1000000, 39 | MemberOnlyRead: true, 40 | Policy: generatePolicy(a.Readers, orgs), 41 | } 42 | collection = append(collection, elem) 43 | } 44 | } 45 | 46 | b, err := json.MarshalIndent(collection, "", " ") 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | err = os.WriteFile("collections.json", b, 0644) 52 | if err != nil { 53 | fmt.Println(err) 54 | return 55 | } 56 | } 57 | 58 | func generatePolicy(readers []string, orgs ArrayFlags) string { 59 | firstElem := true 60 | policy := "OR(" 61 | for _, r := range readers { 62 | if len(orgs) > 0 { 63 | found := false 64 | for _, o := range orgs { 65 | if r == o { 66 | found = true 67 | break 68 | } 69 | } 70 | if !found { 71 | continue 72 | } 73 | } 74 | if !firstElem { 75 | policy += ", " 76 | } 77 | policy += fmt.Sprintf("'%s.member'", r) 78 | firstElem = false 79 | } 80 | policy += ")" 81 | return policy 82 | } 83 | -------------------------------------------------------------------------------- /chaincode/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger-labs/cc-tools-demo/chaincode 2 | 3 | go 1.21 4 | 5 | // replace github.com/hyperledger-labs/cc-tools => ../../cc-tools 6 | 7 | require ( 8 | github.com/cucumber/godog v0.12.6 9 | github.com/hyperledger-labs/cc-tools v1.0.0 10 | github.com/hyperledger/fabric-chaincode-go v0.0.0-20210603161043-af0e3898842a 11 | github.com/hyperledger/fabric-protos-go v0.0.0-20210528200356-82833ecdac31 12 | ) 13 | 14 | require ( 15 | github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect 16 | github.com/cucumber/messages-go/v16 v16.0.1 // indirect 17 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 18 | github.com/golang/protobuf v1.5.3 // indirect 19 | github.com/google/uuid v1.3.0 // indirect 20 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 21 | github.com/hashicorp/go-memdb v1.3.4 // indirect 22 | github.com/hashicorp/golang-lru v0.5.4 // indirect 23 | github.com/hashicorp/hcl v1.0.0 // indirect 24 | github.com/hyperledger/fabric v2.1.1+incompatible // indirect 25 | github.com/magiconair/properties v1.8.7 // indirect 26 | github.com/miekg/pkcs11 v1.0.3 // indirect 27 | github.com/mitchellh/mapstructure v1.3.2 // indirect 28 | github.com/pelletier/go-toml v1.8.0 // indirect 29 | github.com/pkg/errors v0.9.1 // indirect 30 | github.com/spf13/afero v1.9.2 // indirect 31 | github.com/spf13/cast v1.3.1 // indirect 32 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 33 | github.com/spf13/pflag v1.0.5 // indirect 34 | github.com/stretchr/testify v1.9.0 // indirect 35 | github.com/subosito/gotenv v1.6.0 // indirect 36 | github.com/sykesm/zap-logfmt v0.0.4 // indirect 37 | go.uber.org/atomic v1.6.0 // indirect 38 | go.uber.org/multierr v1.5.0 // indirect 39 | go.uber.org/zap v1.16.0 // indirect 40 | golang.org/x/crypto v0.23.0 // indirect 41 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect 42 | golang.org/x/net v0.25.0 // indirect 43 | golang.org/x/sys v0.20.0 // indirect 44 | golang.org/x/text v0.15.0 // indirect 45 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect 46 | google.golang.org/grpc v1.57.0 // indirect 47 | google.golang.org/protobuf v1.34.2 // indirect 48 | gopkg.in/ini.v1 v1.67.0 // indirect 49 | honnef.co/go/tools v0.0.1-2020.1.4 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /chaincode/header/header.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | var Name = "CC Tools Demo" 4 | var Version = "1.0.0" 5 | var Colors = map[string][]string{ 6 | "@default": {"#4267B2", "#34495E", "#ECF0F1"}, 7 | } 8 | var Title = map[string]string{ 9 | "@default": "CC Tools Demo", 10 | } 11 | -------------------------------------------------------------------------------- /chaincode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/hyperledger-labs/cc-tools-demo/chaincode/assettypes" 11 | "github.com/hyperledger-labs/cc-tools-demo/chaincode/datatypes" 12 | "github.com/hyperledger-labs/cc-tools-demo/chaincode/header" 13 | "github.com/hyperledger-labs/cc-tools/assets" 14 | "github.com/hyperledger-labs/cc-tools/events" 15 | sw "github.com/hyperledger-labs/cc-tools/stubwrapper" 16 | tx "github.com/hyperledger-labs/cc-tools/transactions" 17 | 18 | "github.com/hyperledger/fabric-chaincode-go/shim" 19 | pb "github.com/hyperledger/fabric-protos-go/peer" 20 | ) 21 | 22 | var startupCheckExecuted = false 23 | 24 | func SetupCC() error { 25 | tx.InitHeader(tx.Header{ 26 | Name: header.Name, 27 | Version: header.Version, 28 | Colors: header.Colors, 29 | Title: header.Title, 30 | }) 31 | 32 | assets.InitDynamicAssetTypeConfig(assettypes.DynamicAssetTypes) 33 | 34 | tx.InitTxList(txList) 35 | 36 | err := assets.CustomDataTypes(datatypes.CustomDataTypes) 37 | if err != nil { 38 | fmt.Printf("Error injecting custom data types: %s", err) 39 | return err 40 | } 41 | assets.InitAssetList(append(assetTypeList, assettypes.CustomAssets...)) 42 | 43 | events.InitEventList(eventTypeList) 44 | 45 | return nil 46 | } 47 | 48 | // main function starts up the chaincode in the container during instantiate 49 | func main() { 50 | // Generate collection json 51 | genFlag := flag.Bool("g", false, "Enable collection generation") 52 | flag.Bool("orgs", false, "List of orgs to generate collection for") 53 | flag.Parse() 54 | if *genFlag { 55 | listOrgs := flag.Args() 56 | generateCollection(listOrgs) 57 | return 58 | } 59 | 60 | log.Printf("Starting chaincode %s version %s\n", header.Name, header.Version) 61 | 62 | err := SetupCC() 63 | if err != nil { 64 | return 65 | } 66 | 67 | if os.Getenv("RUN_CCAAS") == "true" { 68 | err = runCCaaS() 69 | } else { 70 | err = shim.Start(new(CCDemo)) 71 | } 72 | 73 | if err != nil { 74 | fmt.Printf("Error starting chaincode: %s", err) 75 | } 76 | } 77 | 78 | func runCCaaS() error { 79 | address := os.Getenv("CHAINCODE_SERVER_ADDRESS") 80 | ccid := os.Getenv("CHAINCODE_ID") 81 | 82 | tlsProps, err := getTLSProperties() 83 | if err != nil { 84 | return err 85 | } 86 | 87 | server := &shim.ChaincodeServer{ 88 | CCID: ccid, 89 | Address: address, 90 | CC: new(CCDemo), 91 | TLSProps: *tlsProps, 92 | } 93 | 94 | return server.Start() 95 | } 96 | 97 | func getTLSProperties() (*shim.TLSProperties, error) { 98 | if enableTLS := os.Getenv("TLS_ENABLED"); enableTLS != "true" { 99 | return &shim.TLSProperties{ 100 | Disabled: true, 101 | }, nil 102 | } 103 | 104 | log.Printf("TLS enabled") 105 | 106 | // Get key 107 | keyPath := os.Getenv("KEY_PATH") 108 | key, err := os.ReadFile(keyPath) 109 | 110 | if err != nil { 111 | fmt.Println("Failed to read key file") 112 | return nil, err 113 | } 114 | 115 | // Get cert 116 | certPath := os.Getenv("CERT_PATH") 117 | cert, err := os.ReadFile(certPath) 118 | if err != nil { 119 | fmt.Println("Failed to read cert file") 120 | return nil, err 121 | } 122 | 123 | // Get CA cert 124 | clientCertPath := os.Getenv("CA_CERT_PATH") 125 | caCert, err := os.ReadFile(clientCertPath) 126 | if err != nil { 127 | fmt.Println("Failed to read CA cert file") 128 | return nil, err 129 | } 130 | 131 | return &shim.TLSProperties{ 132 | Disabled: false, 133 | Key: key, 134 | Cert: cert, 135 | ClientCACerts: caCert, 136 | }, nil 137 | } 138 | 139 | // CCDemo implements the shim.Chaincode interface 140 | type CCDemo struct{} 141 | 142 | // Init is called during chaincode instantiation to initialize any 143 | // data. Note that chaincode upgrade also calls this function to reset 144 | // or to migrate data. 145 | func (t *CCDemo) Init(stub shim.ChaincodeStubInterface) (response pb.Response) { 146 | 147 | res := InitFunc(stub) 148 | startupCheckExecuted = true 149 | if res.Status != 200 { 150 | return res 151 | } 152 | 153 | response = shim.Success(nil) 154 | return 155 | } 156 | 157 | func InitFunc(stub shim.ChaincodeStubInterface) (response pb.Response) { 158 | // Defer logging function 159 | defer logTx(stub, time.Now(), &response) 160 | 161 | if assettypes.DynamicAssetTypes.Enabled { 162 | sw := &sw.StubWrapper{ 163 | Stub: stub, 164 | } 165 | err := assets.RestoreAssetList(sw, true) 166 | if err != nil { 167 | response = err.GetErrorResponse() 168 | return 169 | } 170 | } 171 | 172 | err := assets.StartupCheck() 173 | if err != nil { 174 | response = err.GetErrorResponse() 175 | return 176 | } 177 | 178 | err = tx.StartupCheck() 179 | if err != nil { 180 | response = err.GetErrorResponse() 181 | return 182 | } 183 | 184 | response = shim.Success(nil) 185 | return 186 | } 187 | 188 | // Invoke is called per transaction on the chaincode. 189 | func (t *CCDemo) Invoke(stub shim.ChaincodeStubInterface) (response pb.Response) { 190 | // Defer logging function 191 | defer logTx(stub, time.Now(), &response) 192 | 193 | if !startupCheckExecuted { 194 | fmt.Println("Running startup check...") 195 | res := InitFunc(stub) 196 | if res.Status != 200 { 197 | return res 198 | } 199 | startupCheckExecuted = true 200 | } 201 | 202 | var result []byte 203 | 204 | result, err := tx.Run(stub) 205 | 206 | if err != nil { 207 | response = err.GetErrorResponse() 208 | return 209 | } 210 | response = shim.Success([]byte(result)) 211 | return 212 | } 213 | 214 | func logTx(stub shim.ChaincodeStubInterface, beginTime time.Time, response *pb.Response) { 215 | fn, _ := stub.GetFunctionAndParameters() 216 | log.Printf("%d %s %s %s\n", response.Status, fn, time.Since(beginTime), response.Message) 217 | } 218 | -------------------------------------------------------------------------------- /chaincode/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | 8 | "github.com/hyperledger-labs/cc-tools/mock" 9 | ) 10 | 11 | func TestMain(m *testing.M) { 12 | log.SetFlags(log.Lshortfile) 13 | 14 | err := SetupCC() 15 | if err != nil { 16 | log.Println(err) 17 | os.Exit(1) 18 | } 19 | 20 | stub := mock.NewMockStub("org1MSP", new(CCDemo)) 21 | res := stub.MockInit("testInit", [][]byte{[]byte("init")}) 22 | if res.GetStatus() != 200 { 23 | log.Println(res.GetMessage()) 24 | os.Exit(1) 25 | } 26 | 27 | os.Exit(m.Run()) 28 | } 29 | -------------------------------------------------------------------------------- /chaincode/tests/features/createNewLibrary.feature: -------------------------------------------------------------------------------- 1 | Feature: Create New Library 2 | In order to create a new library 3 | As an API client 4 | I want to make a request with the name of the desired library 5 | 6 | Scenario: Create a new library 7 | Given there is a running "" test network from scratch 8 | When I make a "POST" request to "/api/invoke/createNewLibrary" on port 80 with: 9 | """ 10 | { 11 | "name": "Elizabeth's Library" 12 | } 13 | """ 14 | Then the response code should be 200 15 | And the response should have: 16 | """ 17 | { 18 | "@key": "library:9cf6726a-a327-568a-baf1-5881393073bf", 19 | "@lastTouchBy": "orgMSP", 20 | "@lastTx": "createNewLibrary", 21 | "@assetType": "library", 22 | "name": "Elizabeth's Library" 23 | } 24 | """ 25 | 26 | Scenario: Try to create a new library with a name that already exists 27 | Given there is a running "" test network 28 | Given there is a library with name "John's Library" 29 | When I make a "POST" request to "/api/invoke/createNewLibrary" on port 80 with: 30 | """ 31 | { 32 | "name": "John's Library" 33 | } 34 | """ 35 | Then the response code should be 409 36 | -------------------------------------------------------------------------------- /chaincode/tests/features/getBooksByAuthor.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Books By Author 2 | In order to get all the books by an author 3 | As an API client 4 | I want to make a request to the getBooksByAuthor transaction 5 | And receive the appropriate books 6 | 7 | Scenario: Request an author with multiple books 8 | Given there is a running "" test network 9 | And there are 3 books with prefix "book" by author "Jack" 10 | When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: 11 | """ 12 | { 13 | "authorName": "Jack" 14 | } 15 | """ 16 | Then the response code should be 200 17 | And the "result" field should have size 3 18 | 19 | Scenario: Request an author with no books 20 | Given there is a running "" test network 21 | When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: 22 | """ 23 | { 24 | "authorName": "Mary" 25 | } 26 | """ 27 | Then the response code should be 200 28 | And the "result" field should have size 0 29 | 30 | Scenario: Request an author with 2 books while there are other authors with more books 31 | Given there is a running "" test network 32 | Given there are 1 books with prefix "fantasy" by author "Missy" 33 | Given there are 2 books with prefix "cook" by author "John" 34 | When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: 35 | """ 36 | { 37 | "authorName": "John" 38 | } 39 | """ 40 | Then the response code should be 200 41 | And the "result" field should have size 2 -------------------------------------------------------------------------------- /chaincode/tests/features/getNumberOfBooksFromLibrary.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Number Of Books From Library 2 | In order to create the number of books from library 3 | As an API client 4 | I want to make a request 5 | 6 | Scenario: Query Get Number Of Books From Library that Exists 7 | Given there is a running "" test network 8 | And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: 9 | """ 10 | { 11 | "asset": [ 12 | { 13 | "@assetType": "book", 14 | "title": "Meu Nome é Maria", 15 | "author": "Maria Viana" 16 | } 17 | ] 18 | } 19 | """ 20 | And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: 21 | """ 22 | { 23 | "asset": [{ 24 | "@assetType": "library", 25 | "name": "Maria's Library", 26 | "books": [ 27 | { 28 | "@assetType": "book", 29 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5" 30 | } 31 | ] 32 | }] 33 | } 34 | """ 35 | When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 80 with: 36 | """ 37 | { 38 | "library": { 39 | "@key": "library:3cab201f-9e2b-579d-b7b2-72297ed17f49", 40 | "@assetType": "library" 41 | } 42 | } 43 | """ 44 | Then the response code should be 200 45 | And the response should have: 46 | """ 47 | { 48 | "numberOfBooks": 1.0 49 | } 50 | """ 51 | 52 | Scenario: Query Get Number Of Books From Library that Does Not Exists 53 | Given there is a running "" test network 54 | When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 80 with: 55 | """ 56 | { 57 | "library": { 58 | "@key": "library:5c5b201f-9e4c-579d-b7b2-72297ed17f78", 59 | "@assetType": "library" 60 | } 61 | } 62 | """ 63 | Then the response code should be 400 -------------------------------------------------------------------------------- /chaincode/tests/features/updateBookTentant.feature: -------------------------------------------------------------------------------- 1 | Feature: Update Book Tentant 2 | In order to update book tentant 3 | As an API client 4 | I want to make a request 5 | 6 | Scenario: Update Book With A Existing Tentant 7 | # The first 3 statements will be used by all scenarios on this feature 8 | Given there is a running "" test network 9 | And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: 10 | """ 11 | { 12 | "asset": [{ 13 | "@assetType": "book", 14 | "title": "Meu Nome é Maria", 15 | "author": "Maria Viana" 16 | }] 17 | } 18 | """ 19 | And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: 20 | """ 21 | { 22 | "asset": [{ 23 | "@assetType": "person", 24 | "name": "Maria", 25 | "id": "31820792048" 26 | }] 27 | } 28 | """ 29 | When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 80 with: 30 | """ 31 | { 32 | "book": { 33 | "@assetType": "book", 34 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5" 35 | }, 36 | "tenant": { 37 | "@assetType": "person", 38 | "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19" 39 | } 40 | } 41 | """ 42 | Then the response code should be 200 43 | And the response should have: 44 | """ 45 | { 46 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 47 | "@lastTouchBy": "orgMSP", 48 | "@lastTx": "updateBookTenant", 49 | "currentTenant": { 50 | "@assetType": "person", 51 | "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19" 52 | } 53 | } 54 | """ 55 | 56 | Scenario: Update Book With A Not Existing Tentant 57 | Given there is a running "" test network 58 | When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 80 with: 59 | """ 60 | { 61 | "book": { 62 | "@assetType": "book", 63 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5" 64 | }, 65 | "tenant": { 66 | "@assetType": "person", 67 | "@key": "person:56891146-c6866-51a1-844a-bf0b17cb5e19" 68 | } 69 | } 70 | """ 71 | Then the response code should be 404 -------------------------------------------------------------------------------- /chaincode/tests/main_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | 8 | "github.com/cucumber/godog" 9 | "github.com/cucumber/godog/colors" 10 | ) 11 | 12 | var opts = godog.Options{ 13 | Output: colors.Colored(os.Stdout), 14 | Format: Format(), 15 | StopOnFailure: true, 16 | } 17 | 18 | func init() { 19 | godog.BindCommandLineFlags("godog.", &opts) 20 | } 21 | 22 | func TestMain(m *testing.M) { 23 | flag.Parse() 24 | opts.Paths = flag.Args() 25 | 26 | status := godog.TestSuite{ 27 | Name: "cc-tools-demo cucumber tests", 28 | TestSuiteInitializer: InitializeTestSuite, 29 | ScenarioInitializer: InitializeScenario, 30 | Options: &opts, 31 | }.Run() 32 | 33 | os.Exit(status) 34 | } 35 | 36 | func Format() string { 37 | format := "progress" 38 | for _, arg := range os.Args[1:] { 39 | if arg == "-test.v=true" { // go test transforms -v option 40 | format = "pretty" 41 | break 42 | } 43 | } 44 | return format 45 | } 46 | -------------------------------------------------------------------------------- /chaincode/txList.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | txdefs "github.com/hyperledger-labs/cc-tools-demo/chaincode/txdefs" 5 | 6 | tx "github.com/hyperledger-labs/cc-tools/transactions" 7 | ) 8 | 9 | var txList = []tx.Transaction{ 10 | tx.CreateAsset, 11 | tx.UpdateAsset, 12 | tx.DeleteAsset, 13 | txdefs.CreateNewLibrary, 14 | txdefs.GetNumberOfBooksFromLibrary, 15 | txdefs.UpdateBookTenant, 16 | txdefs.GetBooksByAuthor, 17 | } 18 | -------------------------------------------------------------------------------- /chaincode/txdefs/createNewLibrary.go: -------------------------------------------------------------------------------- 1 | package txdefs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/hyperledger-labs/cc-tools/accesscontrol" 8 | "github.com/hyperledger-labs/cc-tools/assets" 9 | "github.com/hyperledger-labs/cc-tools/errors" 10 | "github.com/hyperledger-labs/cc-tools/events" 11 | sw "github.com/hyperledger-labs/cc-tools/stubwrapper" 12 | tx "github.com/hyperledger-labs/cc-tools/transactions" 13 | ) 14 | 15 | // Create a new Library on channel 16 | // POST Method 17 | var CreateNewLibrary = tx.Transaction{ 18 | Tag: "createNewLibrary", 19 | Label: "Create New Library", 20 | Description: "Create a New Library", 21 | Method: "POST", 22 | Callers: []accesscontrol.Caller{ // Only org3 admin can call this transaction 23 | { 24 | MSP: "org3MSP", 25 | OU: "admin", 26 | }, 27 | { 28 | MSP: "orgMSP", 29 | OU: "admin", 30 | }, 31 | }, 32 | 33 | Args: []tx.Argument{ 34 | { 35 | Tag: "name", 36 | Label: "Name", 37 | Description: "Name of the library", 38 | DataType: "string", 39 | Required: true, 40 | }, 41 | }, 42 | Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { 43 | name, _ := req["name"].(string) 44 | 45 | libraryMap := make(map[string]interface{}) 46 | libraryMap["@assetType"] = "library" 47 | libraryMap["name"] = name 48 | 49 | libraryAsset, err := assets.NewAsset(libraryMap) 50 | if err != nil { 51 | return nil, errors.WrapError(err, "Failed to create a new asset") 52 | } 53 | 54 | // Save the new library on channel 55 | _, err = libraryAsset.PutNew(stub) 56 | if err != nil { 57 | return nil, errors.WrapErrorWithStatus(err, "Error saving asset on blockchain", err.Status()) 58 | } 59 | 60 | // Marshal asset back to JSON format 61 | libraryJSON, nerr := json.Marshal(libraryAsset) 62 | if nerr != nil { 63 | return nil, errors.WrapError(nil, "failed to encode asset to JSON format") 64 | } 65 | 66 | // Marshall message to be logged 67 | logMsg, ok := json.Marshal(fmt.Sprintf("New library name: %s", name)) 68 | if ok != nil { 69 | return nil, errors.WrapError(nil, "failed to encode asset to JSON format") 70 | } 71 | 72 | // Call event to log the message 73 | events.CallEvent(stub, "createLibraryLog", logMsg) 74 | 75 | return libraryJSON, nil 76 | }, 77 | } 78 | -------------------------------------------------------------------------------- /chaincode/txdefs/getBooksByAuthor.go: -------------------------------------------------------------------------------- 1 | package txdefs 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/hyperledger-labs/cc-tools/accesscontrol" 7 | "github.com/hyperledger-labs/cc-tools/assets" 8 | "github.com/hyperledger-labs/cc-tools/errors" 9 | sw "github.com/hyperledger-labs/cc-tools/stubwrapper" 10 | tx "github.com/hyperledger-labs/cc-tools/transactions" 11 | ) 12 | 13 | // Return the all books from an specific author 14 | // GET method 15 | var GetBooksByAuthor = tx.Transaction{ 16 | Tag: "getBooksByAuthor", 17 | Label: "Get Books by the Author Name", 18 | Description: "Return all the books from an author", 19 | Method: "GET", 20 | Callers: []accesscontrol.Caller{ // Only org1 and org2 can call this transaction 21 | {MSP: "org1MSP"}, 22 | {MSP: "org2MSP"}, 23 | {MSP: "orgMSP"}, 24 | }, 25 | 26 | Args: []tx.Argument{ 27 | { 28 | Tag: "authorName", 29 | Label: "Author Name", 30 | Description: "Author Name", 31 | DataType: "string", 32 | Required: true, 33 | }, 34 | { 35 | Tag: "limit", 36 | Label: "Limit", 37 | Description: "Limit", 38 | DataType: "number", 39 | }, 40 | }, 41 | Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { 42 | authorName, _ := req["authorName"].(string) 43 | limit, hasLimit := req["limit"].(float64) 44 | 45 | if hasLimit && limit <= 0 { 46 | return nil, errors.NewCCError("limit must be greater than 0", 400) 47 | } 48 | 49 | // Prepare couchdb query 50 | query := map[string]interface{}{ 51 | "selector": map[string]interface{}{ 52 | "@assetType": "book", 53 | "author": authorName, 54 | }, 55 | } 56 | 57 | if hasLimit { 58 | query["limit"] = limit 59 | } 60 | 61 | var err error 62 | response, err := assets.Search(stub, query, "", true) 63 | if err != nil { 64 | return nil, errors.WrapErrorWithStatus(err, "error searching for book's author", 500) 65 | } 66 | 67 | responseJSON, err := json.Marshal(response) 68 | if err != nil { 69 | return nil, errors.WrapErrorWithStatus(err, "error marshaling response", 500) 70 | } 71 | 72 | return responseJSON, nil 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /chaincode/txdefs/getNumberOfBooksFromLibrary.go: -------------------------------------------------------------------------------- 1 | package txdefs 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/hyperledger-labs/cc-tools/accesscontrol" 7 | "github.com/hyperledger-labs/cc-tools/assets" 8 | "github.com/hyperledger-labs/cc-tools/errors" 9 | sw "github.com/hyperledger-labs/cc-tools/stubwrapper" 10 | tx "github.com/hyperledger-labs/cc-tools/transactions" 11 | ) 12 | 13 | // Return the number of books of a library 14 | // GET method 15 | var GetNumberOfBooksFromLibrary = tx.Transaction{ 16 | Tag: "getNumberOfBooksFromLibrary", 17 | Label: "Get Number Of Books From Library", 18 | Description: "Return the number of books of a library", 19 | Method: "GET", 20 | Callers: []accesscontrol.Caller{ // Only org2 can call this transaction 21 | {MSP: "org2MSP"}, 22 | {MSP: "orgMSP"}, 23 | }, 24 | 25 | Args: []tx.Argument{ 26 | { 27 | Tag: "library", 28 | Label: "Library", 29 | Description: "Library", 30 | DataType: "->library", 31 | Required: true, 32 | }, 33 | }, 34 | Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { 35 | libraryKey, _ := req["library"].(assets.Key) 36 | 37 | // Returns Library from channel 38 | libraryMap, err := libraryKey.GetMap(stub) 39 | if err != nil { 40 | return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) 41 | } 42 | 43 | numberOfBooks := 0 44 | books, ok := libraryMap["books"].([]interface{}) 45 | if ok { 46 | numberOfBooks = len(books) 47 | } 48 | 49 | returnMap := make(map[string]interface{}) 50 | returnMap["numberOfBooks"] = numberOfBooks 51 | 52 | // Marshal asset back to JSON format 53 | returnJSON, nerr := json.Marshal(returnMap) 54 | if nerr != nil { 55 | return nil, errors.WrapError(err, "failed to marshal response") 56 | } 57 | 58 | return returnJSON, nil 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /chaincode/txdefs/updateBookTentant.go: -------------------------------------------------------------------------------- 1 | package txdefs 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/hyperledger-labs/cc-tools/accesscontrol" 7 | "github.com/hyperledger-labs/cc-tools/assets" 8 | "github.com/hyperledger-labs/cc-tools/errors" 9 | sw "github.com/hyperledger-labs/cc-tools/stubwrapper" 10 | tx "github.com/hyperledger-labs/cc-tools/transactions" 11 | ) 12 | 13 | // Updates the tenant of a Book 14 | // POST Method 15 | var UpdateBookTenant = tx.Transaction{ 16 | Tag: "updateBookTenant", 17 | Label: "Update Book Tenant", 18 | Description: "Change the tenant of a book", 19 | Method: "PUT", 20 | Callers: []accesscontrol.Caller{ // Any org can call this transaction 21 | {MSP: `$org\dMSP`}, 22 | {MSP: "orgMSP"}, 23 | }, 24 | 25 | Args: []tx.Argument{ 26 | { 27 | Tag: "book", 28 | Label: "Book", 29 | Description: "Book", 30 | DataType: "->book", 31 | Required: true, 32 | }, 33 | { 34 | Tag: "tenant", 35 | Label: "tenant", 36 | Description: "New tenant of the book", 37 | DataType: "->person", 38 | }, 39 | }, 40 | Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { 41 | bookKey, ok := req["book"].(assets.Key) 42 | if !ok { 43 | return nil, errors.WrapError(nil, "Parameter book must be an asset") 44 | } 45 | tenantKey, ok := req["tenant"].(assets.Key) 46 | if !ok { 47 | return nil, errors.WrapError(nil, "Parameter tenant must be an asset") 48 | } 49 | 50 | // Returns Book from channel 51 | bookAsset, err := bookKey.Get(stub) 52 | if err != nil { 53 | return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) 54 | } 55 | bookMap := (map[string]interface{})(*bookAsset) 56 | 57 | // Returns person from channel 58 | tenantAsset, err := tenantKey.Get(stub) 59 | if err != nil { 60 | return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) 61 | } 62 | tenantMap := (map[string]interface{})(*tenantAsset) 63 | 64 | updatedTenantKey := make(map[string]interface{}) 65 | updatedTenantKey["@assetType"] = "person" 66 | updatedTenantKey["@key"] = tenantMap["@key"] 67 | 68 | // Update data 69 | bookMap["currentTenant"] = updatedTenantKey 70 | 71 | bookMap, err = bookAsset.Update(stub, bookMap) 72 | if err != nil { 73 | return nil, errors.WrapError(err, "failed to update asset") 74 | } 75 | 76 | // Marshal asset back to JSON format 77 | bookJSON, nerr := json.Marshal(bookMap) 78 | if nerr != nil { 79 | return nil, errors.WrapError(err, "failed to marshal response") 80 | } 81 | 82 | return bookJSON, nil 83 | }, 84 | } 85 | -------------------------------------------------------------------------------- /chaincode/txdefs_createNewLibrary_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | cc "github.com/hyperledger-labs/cc-tools-demo/chaincode" 11 | "github.com/hyperledger-labs/cc-tools/mock" 12 | ) 13 | 14 | const clientAdminOrg3Cert string = `-----BEGIN CERTIFICATE----- 15 | MIICJjCCAcygAwIBAgIQHv152Ul3TG/REl3mHfYyUjAKBggqhkjOPQQDAjBxMQsw 16 | CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy 17 | YW5jaXNjbzEYMBYGA1UEChMPb3JnLmV4YW1wbGUuY29tMRswGQYDVQQDExJjYS5v 18 | cmcuZXhhbXBsZS5jb20wHhcNMjQwNTA5MjEwOTAwWhcNMzQwNTA3MjEwOTAwWjBq 19 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2Fu 20 | IEZyYW5jaXNjbzEOMAwGA1UECxMFYWRtaW4xHjAcBgNVBAMMFUFkbWluQG9yZy5l 21 | eGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAoAt6mlUBMB0Ab1 22 | paR0ILegN6qKmNfOYR0WV0kGOQwkO4lYcN76lSA2wSlWNTgxtGQDzja1708Ezdr5 23 | vJ5KFhmjTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQk 24 | MCKAID7vB1ct0j2yeNTm45AlCyj9TW22dYtjmPOGq+SVlMKQMAoGCCqGSM49BAMC 25 | A0gAMEUCIQDYol2ylLCcz8qrGJmAFEG/cIG2Kxv8BD5t7Gv/28y8kgIgTz0Y75p6 26 | 3kbL5VN/PCiG2SbX72AVPSiEqj6PSiZJMz4= 27 | -----END CERTIFICATE-----` 28 | 29 | func TestCreateNewLibrary(t *testing.T) { 30 | stub, err := mock.NewMockStubWithCert("org3MSP", new(cc.CCDemo), []byte(clientAdminOrg3Cert)) 31 | if err != nil { 32 | t.FailNow() 33 | } 34 | 35 | expectedResponse := map[string]interface{}{ 36 | "@key": "library:3cab201f-9e2b-579d-b7b2-72297ed17f49", 37 | "@lastTouchBy": "org3MSP", 38 | "@lastTx": "createNewLibrary", 39 | "@assetType": "library", 40 | "name": "Maria's Library", 41 | } 42 | req := map[string]interface{}{ 43 | "name": "Maria's Library", 44 | } 45 | reqBytes, err := json.Marshal(req) 46 | if err != nil { 47 | t.FailNow() 48 | } 49 | 50 | res := stub.MockInvoke("createNewLibrary", [][]byte{ 51 | []byte("createNewLibrary"), 52 | reqBytes, 53 | }) 54 | 55 | expectedResponse["@lastUpdated"] = stub.TxTimestamp.AsTime().Format(time.RFC3339) 56 | 57 | if res.GetStatus() != 200 { 58 | log.Println(res) 59 | t.FailNow() 60 | } 61 | 62 | var resPayload map[string]interface{} 63 | err = json.Unmarshal(res.GetPayload(), &resPayload) 64 | if err != nil { 65 | log.Println(err) 66 | t.FailNow() 67 | } 68 | 69 | if !reflect.DeepEqual(resPayload, expectedResponse) { 70 | log.Println("these should be equal") 71 | log.Printf("%#v\n", resPayload) 72 | log.Printf("%#v\n", expectedResponse) 73 | t.FailNow() 74 | } 75 | 76 | var state map[string]interface{} 77 | stateBytes := stub.State["library:3cab201f-9e2b-579d-b7b2-72297ed17f49"] 78 | err = json.Unmarshal(stateBytes, &state) 79 | if err != nil { 80 | log.Println(err) 81 | t.FailNow() 82 | } 83 | 84 | if !reflect.DeepEqual(state, expectedResponse) { 85 | log.Println("these should be equal") 86 | log.Printf("%#v\n", state) 87 | log.Printf("%#v\n", expectedResponse) 88 | t.FailNow() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /chaincode/txdefs_getNumberOfBooksFromLibrary_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/hyperledger-labs/cc-tools/mock" 10 | ) 11 | 12 | const clientUserOrg2Cert string = `-----BEGIN CERTIFICATE----- 13 | MIICKzCCAdGgAwIBAgIRANlrNyW+FJdC1n3b2uqAH+gwCgYIKoZIzj0EAwIwczEL 14 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 15 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh 16 | Lm9yZzIuZXhhbXBsZS5jb20wHhcNMjMwOTEyMjEwOTAwWhcNMzMwOTA5MjEwOTAw 17 | WjBsMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN 18 | U2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MR8wHQYDVQQDDBZVc2VyMUBv 19 | cmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfQ8ISNJB 20 | MhJqUjjtKkkiDhmOElLMabjhw7K/4D5tKfwHilY7rOWV+XRUDKFAxw2f2ImW3AAs 21 | cAo6shS2jPyLI6NNMEswDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwKwYD 22 | VR0jBCQwIoAgMFP0bQ6QE7la/v0AXYw7boHMnjisxfjhxYak1g1DhBwwCgYIKoZI 23 | zj0EAwIDSAAwRQIhAJOeweSEKwdUZckzTv31n6Sfjsl4tXF2eyqA0tsL/voHAiBF 24 | HYqjQ2S+f++Bjv/kFLvhdY7/acdJYsWH2xyO1XseeA== 25 | -----END CERTIFICATE-----` 26 | 27 | func TestGetNumberOfBooksFromLibrary(t *testing.T) { 28 | stub, err := mock.NewMockStubWithCert("org2MSP", new(CCDemo), []byte(clientUserOrg2Cert)) 29 | if err != nil { 30 | t.FailNow() 31 | } 32 | 33 | // Setup state 34 | setupBook := map[string]interface{}{ 35 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 36 | "@lastTouchBy": "org2MSP", 37 | "@lastTx": "createAsset", 38 | "@assetType": "book", 39 | "title": "Meu Nome é Maria", 40 | "author": "Maria Viana", 41 | "genres": []interface{}{"biography", "non-fiction"}, 42 | "published": "2019-05-06T22:12:41Z", 43 | } 44 | setupLibrary := map[string]interface{}{ 45 | "@key": "library:3cab201f-9e2b-579d-b7b2-72297ed17f49", 46 | "@lastTouchBy": "org3MSP", 47 | "@lastTx": "createNewLibrary", 48 | "@assetType": "library", 49 | "name": "Maria's Library", 50 | "books": []map[string]interface{}{ 51 | { 52 | "@assetType": "book", 53 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 54 | }, 55 | }, 56 | } 57 | setupBookJSON, _ := json.Marshal(setupBook) 58 | setupLibraryJSON, _ := json.Marshal(setupLibrary) 59 | 60 | stub.MockTransactionStart("setupGetNumberOfBooksFromLibrary") 61 | stub.PutState("book:a36a2920-c405-51c3-b584-dcd758338cb5", setupBookJSON) 62 | stub.PutState("library:3cab201f-9e2b-579d-b7b2-72297ed17f49", setupLibraryJSON) 63 | refIdx, err := stub.CreateCompositeKey("book:a36a2920-c405-51c3-b584-dcd758338cb5", []string{"library:3cab201f-9e2b-579d-b7b2-72297ed17f49"}) 64 | if err != nil { 65 | log.Println(err) 66 | t.FailNow() 67 | } 68 | stub.PutState(refIdx, []byte{0x00}) 69 | stub.MockTransactionEnd("setupGetNumberOfBooksFromLibrary") 70 | 71 | expectedResponse := map[string]interface{}{ 72 | "numberOfBooks": 1.0, 73 | } 74 | req := map[string]interface{}{ 75 | "library": map[string]interface{}{ 76 | "name": "Maria's Library", 77 | }, 78 | } 79 | reqBytes, err := json.Marshal(req) 80 | if err != nil { 81 | t.FailNow() 82 | } 83 | 84 | res := stub.MockInvoke("getNumberOfBooksFromLibrary", [][]byte{ 85 | []byte("getNumberOfBooksFromLibrary"), 86 | reqBytes, 87 | }) 88 | 89 | if res.GetStatus() != 200 { 90 | log.Println(res) 91 | t.FailNow() 92 | } 93 | 94 | var resPayload map[string]interface{} 95 | err = json.Unmarshal(res.GetPayload(), &resPayload) 96 | if err != nil { 97 | log.Println(err) 98 | t.FailNow() 99 | } 100 | 101 | if !reflect.DeepEqual(resPayload, expectedResponse) { 102 | log.Println("these should be equal") 103 | log.Printf("%#v\n", resPayload) 104 | log.Printf("%#v\n", expectedResponse) 105 | t.FailNow() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /chaincode/txdefs_updateBookTenant_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/hyperledger-labs/cc-tools/mock" 11 | ) 12 | 13 | // const clientUserOrg1Cert string = `-----BEGIN CERTIFICATE----- 14 | // MIICKjCCAdGgAwIBAgIRAND8OJV+aiUcBgAf79V0Z0gwCgYIKoZIzj0EAwIwczEL 15 | // MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 16 | // cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh 17 | // Lm9yZzEuZXhhbXBsZS5jb20wHhcNMjMwOTEyMjEwOTAwWhcNMzMwOTA5MjEwOTAw 18 | // WjBsMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN 19 | // U2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MR8wHQYDVQQDDBZVc2VyMUBv 20 | // cmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8F737D7W 21 | // IlTT+z19O+UbxKqa9b92j41+zCU7G4rjL/eBHyxBGf77pHXSLxH7ul9ANfmm/eFs 22 | // PURIzoIjz3CynaNNMEswDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwKwYD 23 | // VR0jBCQwIoAghb/LOx4HT2gRIO9/TTfiLucofrmzqF5n1iqtfjKXxVwwCgYIKoZI 24 | // zj0EAwIDRwAwRAIgG3HIEXUXDvnj0Ce7ApBDsW7Rw8ijKrH2T9HKEEd74rkCIBCw 25 | // Gs8rKy8blPYK6H3I64nrylOTvr5Qmkq1Ag2Tqnwb 26 | // -----END CERTIFICATE-----` 27 | 28 | func TestUpdateBookTenant(t *testing.T) { 29 | stub := mock.NewMockStub("org1MSP", new(CCDemo)) 30 | 31 | // State setup 32 | setupPerson := map[string]interface{}{ 33 | "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19", 34 | "@lastTouchBy": "org1MSP", 35 | "@lastTx": "createAsset", 36 | "@assetType": "person", 37 | "name": "Maria", 38 | "id": "31820792048", 39 | "height": 0.0, 40 | } 41 | setupBook := map[string]interface{}{ 42 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 43 | "@lastTouchBy": "org2MSP", 44 | "@lastTx": "createAsset", 45 | "@assetType": "book", 46 | "title": "Meu Nome é Maria", 47 | "author": "Maria Viana", 48 | // "currentTenant": map[string]interface{}{ 49 | // "@assetType": "person", 50 | // "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19", 51 | // }, 52 | "genres": []interface{}{"biography", "non-fiction"}, 53 | "published": "2019-05-06T22:12:41Z", 54 | } 55 | setupPersonJSON, _ := json.Marshal(setupPerson) 56 | setupBookJSON, _ := json.Marshal(setupBook) 57 | 58 | stub.MockTransactionStart("setupUpdateBookTenant") 59 | stub.PutState("person:47061146-c642-51a1-844a-bf0b17cb5e19", setupPersonJSON) 60 | stub.PutState("book:a36a2920-c405-51c3-b584-dcd758338cb5", setupBookJSON) 61 | stub.MockTransactionEnd("setupUpdateBookTenant") 62 | 63 | req := map[string]interface{}{ 64 | "book": map[string]interface{}{ 65 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 66 | }, 67 | "tenant": map[string]interface{}{ 68 | "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19", 69 | }, 70 | } 71 | reqBytes, _ := json.Marshal(req) 72 | 73 | res := stub.MockInvoke("updateBookTenant", [][]byte{ 74 | []byte("updateBookTenant"), 75 | reqBytes, 76 | }) 77 | 78 | if res.GetStatus() != 200 { 79 | log.Println(res) 80 | t.FailNow() 81 | } 82 | 83 | var resPayload map[string]interface{} 84 | err := json.Unmarshal(res.GetPayload(), &resPayload) 85 | if err != nil { 86 | log.Println(err) 87 | t.FailNow() 88 | } 89 | 90 | expectedResponse := map[string]interface{}{ 91 | "@key": "book:a36a2920-c405-51c3-b584-dcd758338cb5", 92 | "@lastTouchBy": "org1MSP", 93 | "@lastTx": "updateBookTenant", 94 | "@assetType": "book", 95 | "title": "Meu Nome é Maria", 96 | "author": "Maria Viana", 97 | "currentTenant": map[string]interface{}{ 98 | "@assetType": "person", 99 | "@key": "person:47061146-c642-51a1-844a-bf0b17cb5e19", 100 | }, 101 | "genres": []interface{}{"biography", "non-fiction"}, 102 | "published": "2019-05-06T22:12:41Z", 103 | } 104 | 105 | expectedResponse["@lastUpdated"] = stub.TxTimestamp.AsTime().Format(time.RFC3339) 106 | 107 | if !reflect.DeepEqual(resPayload, expectedResponse) { 108 | log.Println("these should be equal") 109 | log.Printf("%#v\n", resPayload) 110 | log.Printf("%#v\n", expectedResponse) 111 | t.FailNow() 112 | } 113 | 114 | var state map[string]interface{} 115 | stateBytes := stub.State["book:a36a2920-c405-51c3-b584-dcd758338cb5"] 116 | err = json.Unmarshal(stateBytes, &state) 117 | if err != nil { 118 | log.Println(err) 119 | t.FailNow() 120 | } 121 | 122 | if !reflect.DeepEqual(state, expectedResponse) { 123 | log.Println("these should be equal") 124 | log.Printf("%#v\n", state) 125 | log.Printf("%#v\n", expectedResponse) 126 | t.FailNow() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /fabric-private-chaincode/README.md: -------------------------------------------------------------------------------- 1 | # Integration with Fabric Private Chaincode 2 | 3 | ## Motivation 4 | 5 | Fabric Private Chaincode (FPC) enhances the privacy and security of smart contracts on Hyperledger Fabric by leveraging Trusted Execution Environments (TEEs), such as Intel SGX. With FPC, chaincode execution is shielded, ensuring that sensitive data and business logic remain confidential—even from the hosting peers. This makes FPC an ideal choice for organizations handling susceptible information, such as financial transactions or medical records, while still benefiting from the transparency and immutability of blockchain. 6 | 7 | By integrating FPC with CC Tools, developers can now enjoy a streamlined workflow for deploying and managing private chaincode. This integration combines the usability of CC Tools with the robust privacy guarantees of FPC, enabling faster development cycles without compromising data security. To learn more about the design and technical details of this integration project, refer to the [design document](https://github.com/hyperledger/fabric-private-chaincode/tree/main/docs/design/integrate-with-cc-tools) for a comprehensive explanation. 8 | 9 | ## Prerequisites 10 | 11 | To be able to use FPC, you are required to have some software components as dependencies to enable the trusted execution of chaincodes. Follow the [getting started guide](https://github.com/hyperledger/fabric-private-chaincode?tab=readme-ov-file#getting-started) on FPC until you can set up the development environment. 12 | 13 | ## Integrating your chaincode with FPC 14 | 15 | As part of the integration project, there is now a tutorial explaining step-by-step how to run your chaincode using cc-tools framework within a trusted execution environment with FPC. Follow the [tutorial](https://github.com/hyperledger/fabric-private-chaincode/tree/main/samples/chaincode/cc-tools-demo) for more. 16 | -------------------------------------------------------------------------------- /fabric/.gitignore: -------------------------------------------------------------------------------- 1 | /channel-artifacts/*.tx 2 | /channel-artifacts/*.block 3 | /ledgers 4 | /ledgers-backup 5 | /channel-artifacts/*.json 6 | /org3-artifacts/crypto-config/* 7 | organizations/fabric-ca/ordererOrg/* 8 | organizations/fabric-ca/org1/* 9 | organizations/fabric-ca/org2/* 10 | organizations/rest-certs/* 11 | organizations/ordererOrganizations/* 12 | organizations/peerOrganizations/* 13 | system-genesis-block/* 14 | *.tar.gz 15 | log.txt 16 | -------------------------------------------------------------------------------- /fabric/addOrg3/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=net 2 | IMAGE_TAG=latest 3 | -------------------------------------------------------------------------------- /fabric/addOrg3/README.md: -------------------------------------------------------------------------------- 1 | ## Adding Org3 to the test network 2 | 3 | You can use the `addOrg3.sh` script to add another organization to the Fabric test network. The `addOrg3.sh` script generates the Org3 crypto material, creates an Org3 organization definition, and adds Org3 to a channel on the test network. 4 | 5 | You first need to run `./network.sh up createChannel` in the `test-network` directory before you can run the `addOrg3.sh` script. 6 | 7 | ``` 8 | ./network.sh up createChannel 9 | cd addOrg3 10 | ./addOrg3.sh up 11 | ``` 12 | 13 | If you used `network.sh` to create a channel other than the default `mychannel`, you need pass that name to the `addorg3.sh` script. 14 | ``` 15 | ./network.sh up createChannel -c channel1 16 | cd addOrg3 17 | ./addOrg3.sh up -c channel1 18 | ``` 19 | 20 | You can also re-run the `addOrg3.sh` script to add Org3 to additional channels. 21 | ``` 22 | cd .. 23 | ./network.sh createChannel -c channel2 24 | cd addOrg3 25 | ./addOrg3.sh up -c channel2 26 | ``` 27 | 28 | For more information, use `./addOrg3.sh -h` to see the `addOrg3.sh` help text. 29 | -------------------------------------------------------------------------------- /fabric/addOrg3/ccp-generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function one_line_pem { 4 | echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`" 5 | } 6 | 7 | function json_ccp { 8 | local PP=$(one_line_pem $4) 9 | local CP=$(one_line_pem $5) 10 | sed -e "s/\${ORG}/$1/" \ 11 | -e "s/\${P0PORT}/$2/" \ 12 | -e "s/\${CAPORT}/$3/" \ 13 | -e "s#\${PEERPEM}#$PP#" \ 14 | -e "s#\${CAPEM}#$CP#" \ 15 | ccp-template.json 16 | } 17 | 18 | function yaml_ccp { 19 | local PP=$(one_line_pem $4) 20 | local CP=$(one_line_pem $5) 21 | sed -e "s/\${ORG}/$1/" \ 22 | -e "s/\${P0PORT}/$2/" \ 23 | -e "s/\${CAPORT}/$3/" \ 24 | -e "s#\${PEERPEM}#$PP#" \ 25 | -e "s#\${CAPEM}#$CP#" \ 26 | ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g' 27 | } 28 | 29 | ORG=3 30 | P0PORT=11051 31 | CAPORT=11054 32 | PEERPEM=../organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem 33 | CAPEM=../organizations/peerOrganizations/org3.example.com/ca/ca.org3.example.com-cert.pem 34 | 35 | echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org3.example.com/connection-org3.json 36 | echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org3.example.com/connection-org3.yaml 37 | -------------------------------------------------------------------------------- /fabric/addOrg3/ccp-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-network-org${ORG}", 3 | "version": "1.0.0", 4 | "client": { 5 | "organization": "Org${ORG}", 6 | "connection": { 7 | "timeout": { 8 | "peer": { 9 | "endorser": "300" 10 | } 11 | } 12 | } 13 | }, 14 | "organizations": { 15 | "Org${ORG}": { 16 | "mspid": "Org${ORG}MSP", 17 | "peers": [ 18 | "peer0.org${ORG}.example.com" 19 | ], 20 | "certificateAuthorities": [ 21 | "ca.org${ORG}.example.com" 22 | ] 23 | } 24 | }, 25 | "peers": { 26 | "peer0.org${ORG}.example.com": { 27 | "url": "grpcs://localhost:${P0PORT}", 28 | "tlsCACerts": { 29 | "pem": "${PEERPEM}" 30 | }, 31 | "grpcOptions": { 32 | "ssl-target-name-override": "peer0.org${ORG}.example.com", 33 | "hostnameOverride": "peer0.org${ORG}.example.com" 34 | } 35 | } 36 | }, 37 | "certificateAuthorities": { 38 | "ca.org${ORG}.example.com": { 39 | "url": "https://localhost:${CAPORT}", 40 | "caName": "ca-org${ORG}", 41 | "tlsCACerts": { 42 | "pem": "${CAPEM}" 43 | }, 44 | "httpOptions": { 45 | "verify": false 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fabric/addOrg3/ccp-template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test-network-org${ORG} 3 | version: 1.0.0 4 | client: 5 | organization: Org${ORG} 6 | connection: 7 | timeout: 8 | peer: 9 | endorser: '300' 10 | organizations: 11 | Org${ORG}: 12 | mspid: Org${ORG}MSP 13 | peers: 14 | - peer0.org${ORG}.example.com 15 | certificateAuthorities: 16 | - ca.org${ORG}.example.com 17 | peers: 18 | peer0.org${ORG}.example.com: 19 | url: grpcs://localhost:${P0PORT} 20 | tlsCACerts: 21 | pem: | 22 | ${PEERPEM} 23 | grpcOptions: 24 | ssl-target-name-override: peer0.org${ORG}.example.com 25 | hostnameOverride: peer0.org${ORG}.example.com 26 | certificateAuthorities: 27 | ca.org${ORG}.example.com: 28 | url: https://localhost:${CAPORT} 29 | caName: ca-org${ORG} 30 | tlsCACerts: 31 | pem: | 32 | ${CAPEM} 33 | httpOptions: 34 | verify: false 35 | -------------------------------------------------------------------------------- /fabric/addOrg3/configtx.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | --- 7 | ################################################################################ 8 | # 9 | # Section: Organizations 10 | # 11 | # - This section defines the different organizational identities which will 12 | # be referenced later in the configuration. 13 | # 14 | ################################################################################ 15 | Organizations: 16 | - &org3 17 | # DefaultOrg defines the organization which is used in the sampleconfig 18 | # of the fabric.git development environment 19 | Name: org3MSP 20 | 21 | # ID to load the MSP definition as 22 | ID: org3MSP 23 | 24 | MSPDir: ../organizations/peerOrganizations/org3.example.com/msp 25 | 26 | Policies: 27 | Readers: 28 | Type: Signature 29 | Rule: "OR('org3MSP.admin', 'org3MSP.peer', 'org3MSP.client')" 30 | Writers: 31 | Type: Signature 32 | Rule: "OR('org3MSP.admin', 'org3MSP.client')" 33 | Admins: 34 | Type: Signature 35 | Rule: "OR('org3MSP.admin')" 36 | Endorsement: 37 | Type: Signature 38 | Rule: "OR('org3MSP.peer')" 39 | -------------------------------------------------------------------------------- /fabric/addOrg3/docker/docker-compose-ca-org3.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | services: 9 | 10 | ca_org3: 11 | image: hyperledger/fabric-ca:$IMAGE_TAG 12 | environment: 13 | - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 14 | - FABRIC_CA_SERVER_CA_NAME=ca-org3 15 | - FABRIC_CA_SERVER_TLS_ENABLED=true 16 | - FABRIC_CA_SERVER_PORT=11054 17 | ports: 18 | - "11054:11054" 19 | command: sh -c 'fabric-ca-server start -b admin:adminpw -d' 20 | volumes: 21 | - ../fabric-ca/org3:/etc/hyperledger/fabric-ca-server 22 | container_name: ca_org3 23 | -------------------------------------------------------------------------------- /fabric/addOrg3/docker/docker-compose-couch-org3.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | networks: 9 | test: 10 | 11 | services: 12 | couchdb4: 13 | container_name: couchdb4 14 | image: couchdb:3.1.1 15 | # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password 16 | # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. 17 | environment: 18 | - COUCHDB_USER=admin 19 | - COUCHDB_PASSWORD=adminpw 20 | # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, 21 | # for example map it to utilize Fauxton User Interface in dev environments. 22 | ports: 23 | - "9984:5984" 24 | networks: 25 | - test 26 | 27 | peer0.org3.example.com: 28 | environment: 29 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 30 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb4:5984 31 | # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD 32 | # provide the credentials for ledger to connect to CouchDB. The username and password must 33 | # match the username and password set for the associated CouchDB. 34 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin 35 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw 36 | depends_on: 37 | - couchdb4 38 | networks: 39 | - test 40 | -------------------------------------------------------------------------------- /fabric/addOrg3/docker/docker-compose-org3.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | volumes: 9 | peer0.org3.example.com: 10 | 11 | networks: 12 | test: 13 | 14 | services: 15 | 16 | peer0.org3.example.com: 17 | container_name: peer0.org3.example.com 18 | image: hyperledger/fabric-peer:$IMAGE_TAG 19 | environment: 20 | #Generic peer variables 21 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 22 | # the following setting starts chaincode containers on the same 23 | # bridge network as the peers 24 | # https://docs.docker.com/compose/networking/ 25 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=cc-tools-demo-net 26 | - FABRIC_LOGGING_SPEC=INFO 27 | #- FABRIC_LOGGING_SPEC=DEBUG 28 | - CORE_PEER_TLS_ENABLED=true 29 | - CORE_PEER_PROFILE_ENABLED=true 30 | - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt 31 | - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key 32 | - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt 33 | # Peer specific variabes 34 | - CORE_PEER_ID=peer0.org3.example.com 35 | - CORE_PEER_ADDRESS=peer0.org3.example.com:11051 36 | - CORE_PEER_LISTENADDRESS=0.0.0.0:11051 37 | - CORE_PEER_CHAINCODEADDRESS=peer0.org3.example.com:11052 38 | - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:11052 39 | - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org3.example.com:11051 40 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051 41 | - CORE_PEER_LOCALMSPID=org3MSP 42 | volumes: 43 | - /var/run/docker.sock:/host/var/run/docker.sock 44 | - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp 45 | - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls 46 | - peer0.org3.example.com:/var/hyperledger/production 47 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer 48 | command: peer node start 49 | ports: 50 | - 11051:11051 51 | networks: 52 | - test 53 | -------------------------------------------------------------------------------- /fabric/addOrg3/fabric-ca/registerEnroll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp All Rights Reserved 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | function createOrg3 { 9 | infoln "Enrolling the CA admin" 10 | mkdir -p ../organizations/peerOrganizations/org3.example.com/ 11 | 12 | export FABRIC_CA_CLIENT_HOME=${PWD}/../organizations/peerOrganizations/org3.example.com/ 13 | 14 | set -x 15 | fabric-ca-client enroll -u https://admin:adminpw@localhost:11054 --caname ca-org3 --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 16 | { set +x; } 2>/dev/null 17 | 18 | echo 'NodeOUs: 19 | Enable: true 20 | ClientOUIdentifier: 21 | Certificate: cacerts/localhost-11054-ca-org3.pem 22 | OrganizationalUnitIdentifier: client 23 | PeerOUIdentifier: 24 | Certificate: cacerts/localhost-11054-ca-org3.pem 25 | OrganizationalUnitIdentifier: peer 26 | AdminOUIdentifier: 27 | Certificate: cacerts/localhost-11054-ca-org3.pem 28 | OrganizationalUnitIdentifier: admin 29 | OrdererOUIdentifier: 30 | Certificate: cacerts/localhost-11054-ca-org3.pem 31 | OrganizationalUnitIdentifier: orderer' > ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml 32 | 33 | infoln "Registering peer0" 34 | set -x 35 | fabric-ca-client register --caname ca-org3 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 36 | { set +x; } 2>/dev/null 37 | 38 | infoln "Registering user" 39 | set -x 40 | fabric-ca-client register --caname ca-org3 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 41 | { set +x; } 2>/dev/null 42 | 43 | infoln "Registering the org admin" 44 | set -x 45 | fabric-ca-client register --caname ca-org3 --id.name org3admin --id.secret org3adminpw --id.type admin --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 46 | { set +x; } 2>/dev/null 47 | 48 | infoln "Generating the peer0 msp" 49 | set -x 50 | fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp --csr.hosts peer0.org3.example.com --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 51 | { set +x; } 2>/dev/null 52 | 53 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp/config.yaml 54 | 55 | infoln "Generating the peer0-tls certificates" 56 | set -x 57 | fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls --enrollment.profile tls --csr.hosts peer0.org3.example.com --csr.hosts localhost --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 58 | { set +x; } 2>/dev/null 59 | 60 | 61 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt 62 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/signcerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.crt 63 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/keystore/* ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.key 64 | 65 | mkdir ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/tlscacerts 66 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/tlscacerts/ca.crt 67 | 68 | mkdir ${PWD}/../organizations/peerOrganizations/org3.example.com/tlsca 69 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem 70 | 71 | mkdir ${PWD}/../organizations/peerOrganizations/org3.example.com/ca 72 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp/cacerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/ca/ca.org3.example.com-cert.pem 73 | 74 | infoln "Generating the user msp" 75 | set -x 76 | fabric-ca-client enroll -u https://user1:user1pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 77 | { set +x; } 2>/dev/null 78 | 79 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp/config.yaml 80 | 81 | infoln "Generating the org admin msp" 82 | set -x 83 | fabric-ca-client enroll -u https://org3admin:org3adminpw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem 84 | { set +x; } 2>/dev/null 85 | 86 | cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp/config.yaml 87 | } 88 | -------------------------------------------------------------------------------- /fabric/addOrg3/org3-crypto.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | # --------------------------------------------------------------------------- 7 | # "PeerOrgs" - Definition of organizations managing peer nodes 8 | # --------------------------------------------------------------------------- 9 | PeerOrgs: 10 | # --------------------------------------------------------------------------- 11 | # org3 12 | # --------------------------------------------------------------------------- 13 | - Name: org3 14 | Domain: org3.example.com 15 | EnableNodeOUs: true 16 | Template: 17 | Count: 1 18 | SANS: 19 | - localhost 20 | Users: 21 | Count: 1 22 | -------------------------------------------------------------------------------- /fabric/docker/docker-compose-ca.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | networks: 9 | cc-tools-demo-net: 10 | external: true 11 | 12 | services: 13 | 14 | ca_org: 15 | image: hyperledger/fabric-ca:$IMAGE_TAG 16 | environment: 17 | - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 18 | - FABRIC_CA_SERVER_CA_NAME=ca-org 19 | - FABRIC_CA_SERVER_TLS_ENABLED=true 20 | - FABRIC_CA_SERVER_PORT=7054 21 | ports: 22 | - "7054:7054" 23 | command: sh -c 'fabric-ca-server start -b admin:adminpw -d' 24 | volumes: 25 | - ../organizations/fabric-ca/org:/etc/hyperledger/fabric-ca-server 26 | container_name: ca_org 27 | networks: 28 | - cc-tools-demo-net 29 | 30 | ca_org1: 31 | image: hyperledger/fabric-ca:$IMAGE_TAG 32 | environment: 33 | - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 34 | - FABRIC_CA_SERVER_CA_NAME=ca-org1 35 | - FABRIC_CA_SERVER_TLS_ENABLED=true 36 | - FABRIC_CA_SERVER_PORT=7054 37 | ports: 38 | - "7054:7054" 39 | command: sh -c 'fabric-ca-server start -b admin:adminpw -d' 40 | volumes: 41 | - ../organizations/fabric-ca/org1:/etc/hyperledger/fabric-ca-server 42 | container_name: ca_org1 43 | networks: 44 | - cc-tools-demo-net 45 | 46 | ca_org2: 47 | image: hyperledger/fabric-ca:$IMAGE_TAG 48 | environment: 49 | - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 50 | - FABRIC_CA_SERVER_CA_NAME=ca-org2 51 | - FABRIC_CA_SERVER_TLS_ENABLED=true 52 | - FABRIC_CA_SERVER_PORT=8054 53 | ports: 54 | - "8054:8054" 55 | command: sh -c 'fabric-ca-server start -b admin:adminpw -d' 56 | volumes: 57 | - ../organizations/fabric-ca/org2:/etc/hyperledger/fabric-ca-server 58 | container_name: ca_org2 59 | networks: 60 | - cc-tools-demo-net 61 | 62 | ca_orderer: 63 | image: hyperledger/fabric-ca:$IMAGE_TAG 64 | environment: 65 | - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 66 | - FABRIC_CA_SERVER_CA_NAME=ca-orderer 67 | - FABRIC_CA_SERVER_TLS_ENABLED=true 68 | - FABRIC_CA_SERVER_PORT=9054 69 | ports: 70 | - "9054:9054" 71 | command: sh -c 'fabric-ca-server start -b admin:adminpw -d' 72 | volumes: 73 | - ../organizations/fabric-ca/ordererOrg:/etc/hyperledger/fabric-ca-server 74 | container_name: ca_orderer 75 | networks: 76 | - cc-tools-demo-net 77 | -------------------------------------------------------------------------------- /fabric/docker/docker-compose-couch-org.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | networks: 9 | cc-tools-demo-net: 10 | external: true 11 | 12 | services: 13 | couchdb0: 14 | container_name: couchdb0 15 | image: couchdb:3.1.1 16 | # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password 17 | # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. 18 | environment: 19 | - COUCHDB_USER=admin 20 | - COUCHDB_PASSWORD=adminpw 21 | # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, 22 | # for example map it to utilize Fauxton User Interface in dev environments. 23 | ports: 24 | - "5984:5984" 25 | networks: 26 | - cc-tools-demo-net 27 | 28 | peer0.org.example.com: 29 | environment: 30 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 31 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0:5984 32 | # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD 33 | # provide the credentials for ledger to connect to CouchDB. The username and password must 34 | # match the username and password set for the associated CouchDB. 35 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin 36 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw 37 | depends_on: 38 | - couchdb0 39 | -------------------------------------------------------------------------------- /fabric/docker/docker-compose-couch.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | networks: 9 | cc-tools-demo-net: 10 | external: true 11 | 12 | services: 13 | couchdb0: 14 | container_name: couchdb0 15 | image: couchdb:3.1.1 16 | # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password 17 | # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. 18 | environment: 19 | - COUCHDB_USER=admin 20 | - COUCHDB_PASSWORD=adminpw 21 | # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, 22 | # for example map it to utilize Fauxton User Interface in dev environments. 23 | ports: 24 | - "5984:5984" 25 | networks: 26 | - cc-tools-demo-net 27 | 28 | peer0.org1.example.com: 29 | environment: 30 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 31 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0:5984 32 | # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD 33 | # provide the credentials for ledger to connect to CouchDB. The username and password must 34 | # match the username and password set for the associated CouchDB. 35 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin 36 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw 37 | depends_on: 38 | - couchdb0 39 | 40 | couchdb1: 41 | container_name: couchdb1 42 | image: couchdb:3.1.1 43 | # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password 44 | # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. 45 | environment: 46 | - COUCHDB_USER=admin 47 | - COUCHDB_PASSWORD=adminpw 48 | # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, 49 | # for example map it to utilize Fauxton User Interface in dev environments. 50 | ports: 51 | - "7984:5984" 52 | networks: 53 | - cc-tools-demo-net 54 | 55 | peer0.org2.example.com: 56 | environment: 57 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 58 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb1:5984 59 | # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD 60 | # provide the credentials for ledger to connect to CouchDB. The username and password must 61 | # match the username and password set for the associated CouchDB. 62 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin 63 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw 64 | depends_on: 65 | - couchdb1 66 | 67 | couchdb2: 68 | container_name: couchdb2 69 | image: couchdb:3.1.1 70 | # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password 71 | # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. 72 | environment: 73 | - COUCHDB_USER=admin 74 | - COUCHDB_PASSWORD=adminpw 75 | # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, 76 | # for example map it to utilize Fauxton User Interface in dev environments. 77 | ports: 78 | - "9984:5984" 79 | networks: 80 | - cc-tools-demo-net 81 | 82 | peer0.org3.example.com: 83 | environment: 84 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 85 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb2:5984 86 | # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD 87 | # provide the credentials for ledger to connect to CouchDB. The username and password must 88 | # match the username and password set for the associated CouchDB. 89 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin 90 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw 91 | depends_on: 92 | - couchdb1 -------------------------------------------------------------------------------- /fabric/docker/docker-compose-test-net-org.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | version: '2' 7 | 8 | volumes: 9 | orderer.example.com: 10 | peer0.org.example.com: 11 | 12 | networks: 13 | cc-tools-demo-net: 14 | external: true 15 | 16 | services: 17 | 18 | orderer.example.com: 19 | container_name: orderer.example.com 20 | image: hyperledger/fabric-orderer:$IMAGE_TAG 21 | environment: 22 | - FABRIC_LOGGING_SPEC=INFO 23 | - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 24 | - ORDERER_GENERAL_LISTENPORT=7050 25 | - ORDERER_GENERAL_GENESISMETHOD=file 26 | - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block 27 | - ORDERER_GENERAL_LOCALMSPID=OrdererMSP 28 | - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp 29 | - ORDERER_CHANNELPARTICIPATION_ENABLED=true 30 | - ORDERER_GENERAL_BOOTSTRAPMETHOD=none 31 | # enabled TLS 32 | - ORDERER_GENERAL_TLS_ENABLED=true 33 | - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key 34 | - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt 35 | - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] 36 | - ORDERER_KAFKA_TOPIC_REPLICATIONFACTOR=1 37 | - ORDERER_KAFKA_VERBOSE=true 38 | - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/var/hyperledger/orderer/tls/server.crt 39 | - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/var/hyperledger/orderer/tls/server.key 40 | - ORDERER_GENERAL_CLUSTER_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] 41 | #admin 42 | - ORDERER_ADMIN_TLS_ENABLED=true 43 | - ORDERER_ADMIN_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt 44 | - ORDERER_ADMIN_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key 45 | - ORDERER_ADMIN_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] 46 | - ORDERER_ADMIN_TLS_CLIENTROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] 47 | - ORDERER_ADMIN_LISTENADDRESS=0.0.0.0:7052 48 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric 49 | command: orderer 50 | volumes: 51 | # - ../system-genesis-block/genesis.block:/var/hyperledger/orderer/orderer.genesis.block 52 | - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp 53 | - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls 54 | - orderer.example.com:/var/hyperledger/production/orderer 55 | ports: 56 | - 7050:7050 57 | - 7052:7052 # admin server 58 | networks: 59 | - cc-tools-demo-net 60 | 61 | peer0.org.example.com: 62 | container_name: peer0.org.example.com 63 | image: hyperledger/fabric-peer:$IMAGE_TAG 64 | environment: 65 | #Generic peer variables 66 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 67 | # the following setting starts chaincode containers on the same 68 | # bridge network as the peers 69 | # https://docs.docker.com/compose/networking/ 70 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=cc-tools-demo-net 71 | - FABRIC_LOGGING_SPEC=INFO 72 | #- FABRIC_LOGGING_SPEC=DEBUG 73 | - CORE_PEER_TLS_ENABLED=true 74 | - CORE_PEER_PROFILE_ENABLED=true 75 | - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt 76 | - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key 77 | - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt 78 | # Peer specific variabes 79 | - CORE_PEER_ID=peer0.org.example.com 80 | - CORE_PEER_ADDRESS=peer0.org.example.com:7051 81 | - CORE_PEER_LISTENADDRESS=0.0.0.0:7051 82 | - CORE_PEER_CHAINCODEADDRESS=peer0.org.example.com:7052 83 | - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052 84 | - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org.example.com:7051 85 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org.example.com:7051 86 | - CORE_PEER_LOCALMSPID=orgMSP 87 | - CORE_CHAINCODE_EXTERNALBUILDERS=[{"name":"basic-builder","path":"/etc/hyperledger/external-builder","propagateEnvironment":["CCAAS_CLIENT_CERT_FILE","CCAAS_CLIENT_KEY_FILE","CCAAS_ROOT_CERT_FILE","CCAAS_ORG","CCAAS_PORT"]}] 88 | - CCAAS_CLIENT_CERT_FILE=/etc/hyperledger/fabric/ccaas/tls/client/cert.pem 89 | - CCAAS_CLIENT_KEY_FILE=/etc/hyperledger/fabric/ccaas/tls/client/key.pem 90 | - CCAAS_ROOT_CERT_FILE=/etc/hyperledger/fabric/ccaas/tls/ca/cert.pem 91 | - CCAAS_ORG=org 92 | - CCAAS_PORT=9999 93 | - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp 94 | volumes: 95 | - /var/run/docker.sock:/host/var/run/docker.sock 96 | - ../organizations/peerOrganizations/org.example.com/peers/peer0.org.example.com/msp:/etc/hyperledger/fabric/msp 97 | - ../organizations/peerOrganizations/org.example.com/peers/peer0.org.example.com/tls:/etc/hyperledger/fabric/tls 98 | - ../organizations/ccaas/org.example.com:/etc/hyperledger/fabric/ccaas/tls 99 | - peer0.org.example.com:/var/hyperledger/production 100 | - ../externalBuilder:/etc/hyperledger/external-builder 101 | - ../bin/jq:/usr/local/bin/jq 102 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer 103 | command: peer node start 104 | ports: 105 | - 7051:7051 106 | - 7053:7053 107 | networks: 108 | - cc-tools-demo-net 109 | 110 | cli: 111 | container_name: cli 112 | image: hyperledger/fabric-tools:$IMAGE_TAG 113 | tty: true 114 | stdin_open: true 115 | environment: 116 | - GOPATH=/opt/gopath 117 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 118 | - FABRIC_LOGGING_SPEC=INFO 119 | #- FABRIC_LOGGING_SPEC=DEBUG 120 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer 121 | command: /bin/bash 122 | volumes: 123 | - /var/run/:/host/var/run/ 124 | - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations 125 | - ../scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/ 126 | depends_on: 127 | - peer0.org.example.com 128 | networks: 129 | - cc-tools-demo-net 130 | -------------------------------------------------------------------------------- /fabric/externalBuilder/bin/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set -euo pipefail 4 | 5 | SOURCE=$1 6 | OUTPUT=$3 7 | 8 | #external chaincodes expect connection.json file in the chaincode package 9 | if [ ! -f "$SOURCE/connection.json" ]; then 10 | >&2 echo "$SOURCE/connection.json not found" 11 | exit 1 12 | fi 13 | 14 | #simply copy the endpoint information to specified output location 15 | cp $SOURCE/connection.json $OUTPUT/connection.json 16 | 17 | if [ -d "$SOURCE/metadata" ]; then 18 | cp -a $SOURCE/metadata $OUTPUT/metadata 19 | fi 20 | 21 | exit 0 -------------------------------------------------------------------------------- /fabric/externalBuilder/bin/detect: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | METADIR=$2 6 | # check if the "type" field is set to "external" 7 | # crude way without jq which is not in the default fabric peer image 8 | TYPE=$(tr -d '\n' < "$METADIR/metadata.json" | awk -F':' '{ for (i = 1; i < NF; i++){ if ($i~/type/) { print $(i+1); break }}}'| cut -d\" -f2) 9 | 10 | if [ "$TYPE" = "external" ]; then 11 | exit 0 12 | fi 13 | 14 | exit 1 -------------------------------------------------------------------------------- /fabric/externalBuilder/bin/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | BLD="$1" 6 | RELEASE="$2" 7 | 8 | if [ -d "$BLD/metadata" ]; then 9 | cp -a "$BLD/metadata/"* "$RELEASE/" 10 | fi 11 | 12 | #external chaincodes expect artifacts to be placed under "$RELEASE"/chaincode/server 13 | if [ -f $BLD/connection.json ]; then 14 | mkdir -p "$RELEASE"/chaincode/server 15 | 16 | sed -i "s/{{.org}}/$CCAAS_ORG/" $BLD/connection.json 17 | sed -i "s/{{.port}}/$CCAAS_PORT/" $BLD/connection.json 18 | 19 | # Check if tls is enabled 20 | if [ "$(jq -r .tls_required $BLD/connection.json)" = "true" ]; then 21 | client_cert=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' "$CCAAS_CLIENT_CERT_FILE") 22 | client_key=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' "$CCAAS_CLIENT_KEY_FILE") 23 | root_cert=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' "$CCAAS_ROOT_CERT_FILE") 24 | 25 | # Add certs files to connection.json 26 | echo $(jq ".client_cert=\"$client_cert\"" $BLD/connection.json) > $BLD/connection.json 27 | echo $(jq ".client_key=\"$client_key\"" $BLD/connection.json) > $BLD/connection.json 28 | echo $(jq ".root_cert=\"$root_cert\"" $BLD/connection.json) > $BLD/connection.json 29 | fi 30 | 31 | cp $BLD/connection.json "$RELEASE"/chaincode/server 32 | 33 | exit 0 34 | fi 35 | 36 | exit 1 37 | 38 | -------------------------------------------------------------------------------- /fabric/organizations/ccp-generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function one_line_pem { 4 | echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`" 5 | } 6 | 7 | function json_ccp { 8 | local PP=$(one_line_pem $4) 9 | local CP=$(one_line_pem $5) 10 | sed -e "s/\${ORG}/$1/" \ 11 | -e "s/\${P0PORT}/$2/" \ 12 | -e "s/\${CAPORT}/$3/" \ 13 | -e "s#\${PEERPEM}#$PP#" \ 14 | -e "s#\${CAPEM}#$CP#" \ 15 | organizations/ccp-template.json 16 | } 17 | 18 | function yaml_ccp { 19 | local PP=$(one_line_pem $4) 20 | local CP=$(one_line_pem $5) 21 | sed -e "s/\${ORG}/$1/" \ 22 | -e "s/\${P0PORT}/$2/" \ 23 | -e "s/\${CAPORT}/$3/" \ 24 | -e "s#\${PEERPEM}#$PP#" \ 25 | -e "s#\${CAPEM}#$CP#" \ 26 | organizations/ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g' 27 | } 28 | 29 | # default number of organizartions 30 | ORG_QNTY=3 31 | 32 | while getopts n: opt; do 33 | case $opt in 34 | n) ORG_QNTY=${OPTARG} 35 | ;; 36 | esac 37 | done 38 | 39 | if [ $ORG_QNTY != 3 -a $ORG_QNTY != 1 ] 40 | then 41 | echo 'WARNING: The number of organizations allowed is either 3 or 1.' 42 | echo 'Defaulting to 3 organizations.' 43 | ORG_QNTY=3 44 | fi 45 | 46 | if [ $ORG_QNTY -gt 1 ] 47 | then 48 | ORG=1 49 | P0PORT=7051 50 | CAPORT=7054 51 | PEERPEM=organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem 52 | CAPEM=organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem 53 | 54 | echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org1.example.com/connection-org1.json 55 | echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org1.example.com/connection-org1.yaml 56 | 57 | ORG=2 58 | P0PORT=9051 59 | CAPORT=8054 60 | PEERPEM=organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem 61 | CAPEM=organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem 62 | 63 | echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org2.example.com/connection-org2.json 64 | echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org2.example.com/connection-org2.yaml 65 | else 66 | ORG="" 67 | P0PORT=7051 68 | CAPORT=7054 69 | PEERPEM=organizations/peerOrganizations/org.example.com/tlsca/tlsca.org.example.com-cert.pem 70 | CAPEM=organizations/peerOrganizations/org.example.com/ca/ca.org.example.com-cert.pem 71 | 72 | echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org.example.com/connection-org.json 73 | echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org.example.com/connection-org.yaml 74 | fi 75 | -------------------------------------------------------------------------------- /fabric/organizations/ccp-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-network-org${ORG}", 3 | "version": "1.0.0", 4 | "client": { 5 | "organization": "Org${ORG}", 6 | "connection": { 7 | "timeout": { 8 | "peer": { 9 | "endorser": "300" 10 | } 11 | } 12 | } 13 | }, 14 | "organizations": { 15 | "Org${ORG}": { 16 | "mspid": "Org${ORG}MSP", 17 | "peers": [ 18 | "peer0.org${ORG}.example.com" 19 | ], 20 | "certificateAuthorities": [ 21 | "ca.org${ORG}.example.com" 22 | ] 23 | } 24 | }, 25 | "peers": { 26 | "peer0.org${ORG}.example.com": { 27 | "url": "grpcs://localhost:${P0PORT}", 28 | "tlsCACerts": { 29 | "pem": "${PEERPEM}" 30 | }, 31 | "grpcOptions": { 32 | "ssl-target-name-override": "peer0.org${ORG}.example.com", 33 | "hostnameOverride": "peer0.org${ORG}.example.com" 34 | } 35 | } 36 | }, 37 | "certificateAuthorities": { 38 | "ca.org${ORG}.example.com": { 39 | "url": "https://localhost:${CAPORT}", 40 | "caName": "ca-org${ORG}", 41 | "tlsCACerts": { 42 | "pem": ["${CAPEM}"] 43 | }, 44 | "httpOptions": { 45 | "verify": false 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fabric/organizations/ccp-template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test-network-org${ORG} 3 | version: 1.0.0 4 | client: 5 | organization: Org${ORG} 6 | connection: 7 | timeout: 8 | peer: 9 | endorser: '300' 10 | organizations: 11 | Org${ORG}: 12 | mspid: Org${ORG}MSP 13 | peers: 14 | - peer0.org${ORG}.example.com 15 | certificateAuthorities: 16 | - ca.org${ORG}.example.com 17 | peers: 18 | peer0.org${ORG}.example.com: 19 | url: grpcs://localhost:${P0PORT} 20 | tlsCACerts: 21 | pem: | 22 | ${PEERPEM} 23 | grpcOptions: 24 | ssl-target-name-override: peer0.org${ORG}.example.com 25 | hostnameOverride: peer0.org${ORG}.example.com 26 | certificateAuthorities: 27 | ca.org${ORG}.example.com: 28 | url: https://localhost:${CAPORT} 29 | caName: ca-org${ORG} 30 | tlsCACerts: 31 | pem: 32 | - | 33 | ${CAPEM} 34 | httpOptions: 35 | verify: false 36 | -------------------------------------------------------------------------------- /fabric/organizations/cryptogen/crypto-config-orderer.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | # --------------------------------------------------------------------------- 7 | # "OrdererOrgs" - Definition of organizations managing orderer nodes 8 | # --------------------------------------------------------------------------- 9 | OrdererOrgs: 10 | # --------------------------------------------------------------------------- 11 | # Orderer 12 | # --------------------------------------------------------------------------- 13 | - Name: Orderer 14 | Domain: example.com 15 | EnableNodeOUs: true 16 | # --------------------------------------------------------------------------- 17 | # "Specs" - See PeerOrgs for complete description 18 | # --------------------------------------------------------------------------- 19 | Specs: 20 | - Hostname: orderer 21 | SANS: 22 | - localhost 23 | -------------------------------------------------------------------------------- /fabric/organizations/cryptogen/crypto-config-org.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | 7 | # --------------------------------------------------------------------------- 8 | # "PeerOrgs" - Definition of organizations managing peer nodes 9 | # --------------------------------------------------------------------------- 10 | PeerOrgs: 11 | # --------------------------------------------------------------------------- 12 | # org 13 | # --------------------------------------------------------------------------- 14 | - Name: org 15 | Domain: org.example.com 16 | EnableNodeOUs: true 17 | # --------------------------------------------------------------------------- 18 | # "Specs" 19 | # --------------------------------------------------------------------------- 20 | # Uncomment this section to enable the explicit definition of hosts in your 21 | # configuration. Most users will want to use Template, below 22 | # 23 | # Specs is an array of Spec entries. Each Spec entry consists of two fields: 24 | # - Hostname: (Required) The desired hostname, sans the domain. 25 | # - CommonName: (Optional) Specifies the template or explicit override for 26 | # the CN. By default, this is the template: 27 | # 28 | # "{{.Hostname}}.{{.Domain}}" 29 | # 30 | # which obtains its values from the Spec.Hostname and 31 | # Org.Domain, respectively. 32 | # --------------------------------------------------------------------------- 33 | # - Hostname: foo # implicitly "foo.org.example.com" 34 | # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above 35 | # - Hostname: bar 36 | # - Hostname: baz 37 | # --------------------------------------------------------------------------- 38 | # "Template" 39 | # --------------------------------------------------------------------------- 40 | # Allows for the definition of 1 or more hosts that are created sequentially 41 | # from a template. By default, this looks like "peer%d" from 0 to Count-1. 42 | # You may override the number of nodes (Count), the starting index (Start) 43 | # or the template used to construct the name (Hostname). 44 | # 45 | # Note: Template and Specs are not mutually exclusive. You may define both 46 | # sections and the aggregate nodes will be created for you. Take care with 47 | # name collisions 48 | # --------------------------------------------------------------------------- 49 | Template: 50 | Count: 1 51 | SANS: 52 | - localhost 53 | # Start: 5 54 | # Hostname: {{.Prefix}}{{.Index}} # default 55 | # --------------------------------------------------------------------------- 56 | # "Users" 57 | # --------------------------------------------------------------------------- 58 | # Count: The number of user accounts _in addition_ to Admin 59 | # --------------------------------------------------------------------------- 60 | Users: 61 | Count: 1 62 | -------------------------------------------------------------------------------- /fabric/organizations/cryptogen/crypto-config-org1.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | 7 | # --------------------------------------------------------------------------- 8 | # "PeerOrgs" - Definition of organizations managing peer nodes 9 | # --------------------------------------------------------------------------- 10 | PeerOrgs: 11 | # --------------------------------------------------------------------------- 12 | # org1 13 | # --------------------------------------------------------------------------- 14 | - Name: org1 15 | Domain: org1.example.com 16 | EnableNodeOUs: true 17 | # --------------------------------------------------------------------------- 18 | # "Specs" 19 | # --------------------------------------------------------------------------- 20 | # Uncomment this section to enable the explicit definition of hosts in your 21 | # configuration. Most users will want to use Template, below 22 | # 23 | # Specs is an array of Spec entries. Each Spec entry consists of two fields: 24 | # - Hostname: (Required) The desired hostname, sans the domain. 25 | # - CommonName: (Optional) Specifies the template or explicit override for 26 | # the CN. By default, this is the template: 27 | # 28 | # "{{.Hostname}}.{{.Domain}}" 29 | # 30 | # which obtains its values from the Spec.Hostname and 31 | # Org.Domain, respectively. 32 | # --------------------------------------------------------------------------- 33 | # - Hostname: foo # implicitly "foo.org1.example.com" 34 | # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above 35 | # - Hostname: bar 36 | # - Hostname: baz 37 | # --------------------------------------------------------------------------- 38 | # "Template" 39 | # --------------------------------------------------------------------------- 40 | # Allows for the definition of 1 or more hosts that are created sequentially 41 | # from a template. By default, this looks like "peer%d" from 0 to Count-1. 42 | # You may override the number of nodes (Count), the starting index (Start) 43 | # or the template used to construct the name (Hostname). 44 | # 45 | # Note: Template and Specs are not mutually exclusive. You may define both 46 | # sections and the aggregate nodes will be created for you. Take care with 47 | # name collisions 48 | # --------------------------------------------------------------------------- 49 | Template: 50 | Count: 1 51 | SANS: 52 | - localhost 53 | # Start: 5 54 | # Hostname: {{.Prefix}}{{.Index}} # default 55 | # --------------------------------------------------------------------------- 56 | # "Users" 57 | # --------------------------------------------------------------------------- 58 | # Count: The number of user accounts _in addition_ to Admin 59 | # --------------------------------------------------------------------------- 60 | Users: 61 | Count: 1 62 | -------------------------------------------------------------------------------- /fabric/organizations/cryptogen/crypto-config-org2.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | # --------------------------------------------------------------------------- 7 | # "PeerOrgs" - Definition of organizations managing peer nodes 8 | # --------------------------------------------------------------------------- 9 | PeerOrgs: 10 | # --------------------------------------------------------------------------- 11 | # Org2 12 | # --------------------------------------------------------------------------- 13 | - Name: org2 14 | Domain: org2.example.com 15 | EnableNodeOUs: true 16 | # --------------------------------------------------------------------------- 17 | # "Specs" 18 | # --------------------------------------------------------------------------- 19 | # Uncomment this section to enable the explicit definition of hosts in your 20 | # configuration. Most users will want to use Template, below 21 | # 22 | # Specs is an array of Spec entries. Each Spec entry consists of two fields: 23 | # - Hostname: (Required) The desired hostname, sans the domain. 24 | # - CommonName: (Optional) Specifies the template or explicit override for 25 | # the CN. By default, this is the template: 26 | # 27 | # "{{.Hostname}}.{{.Domain}}" 28 | # 29 | # which obtains its values from the Spec.Hostname and 30 | # Org.Domain, respectively. 31 | # --------------------------------------------------------------------------- 32 | # Specs: 33 | # - Hostname: foo # implicitly "foo.org1.example.com" 34 | # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above 35 | # - Hostname: bar 36 | # - Hostname: baz 37 | # --------------------------------------------------------------------------- 38 | # "Template" 39 | # --------------------------------------------------------------------------- 40 | # Allows for the definition of 1 or more hosts that are created sequentially 41 | # from a template. By default, this looks like "peer%d" from 0 to Count-1. 42 | # You may override the number of nodes (Count), the starting index (Start) 43 | # or the template used to construct the name (Hostname). 44 | # 45 | # Note: Template and Specs are not mutually exclusive. You may define both 46 | # sections and the aggregate nodes will be created for you. Take care with 47 | # name collisions 48 | # --------------------------------------------------------------------------- 49 | Template: 50 | Count: 1 51 | SANS: 52 | - localhost 53 | # Start: 5 54 | # Hostname: {{.Prefix}}{{.Index}} # default 55 | # --------------------------------------------------------------------------- 56 | # "Users" 57 | # --------------------------------------------------------------------------- 58 | # Count: The number of user accounts _in addition_ to Admin 59 | # --------------------------------------------------------------------------- 60 | Users: 61 | Count: 1 62 | -------------------------------------------------------------------------------- /fabric/organizations/cryptogen/crypto-config-org3.yaml: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corp. All Rights Reserved. 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | # --------------------------------------------------------------------------- 7 | # "PeerOrgs" - Definition of organizations managing peer nodes 8 | # --------------------------------------------------------------------------- 9 | PeerOrgs: 10 | # --------------------------------------------------------------------------- 11 | # Org2 12 | # --------------------------------------------------------------------------- 13 | - Name: org3 14 | Domain: org3.example.com 15 | EnableNodeOUs: true 16 | # --------------------------------------------------------------------------- 17 | # "Specs" 18 | # --------------------------------------------------------------------------- 19 | # Uncomment this section to enable the explicit definition of hosts in your 20 | # configuration. Most users will want to use Template, below 21 | # 22 | # Specs is an array of Spec entries. Each Spec entry consists of two fields: 23 | # - Hostname: (Required) The desired hostname, sans the domain. 24 | # - CommonName: (Optional) Specifies the template or explicit override for 25 | # the CN. By default, this is the template: 26 | # 27 | # "{{.Hostname}}.{{.Domain}}" 28 | # 29 | # which obtains its values from the Spec.Hostname and 30 | # Org.Domain, respectively. 31 | # --------------------------------------------------------------------------- 32 | # Specs: 33 | # - Hostname: foo # implicitly "foo.org1.example.com" 34 | # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above 35 | # - Hostname: bar 36 | # - Hostname: baz 37 | # --------------------------------------------------------------------------- 38 | # "Template" 39 | # --------------------------------------------------------------------------- 40 | # Allows for the definition of 1 or more hosts that are created sequentially 41 | # from a template. By default, this looks like "peer%d" from 0 to Count-1. 42 | # You may override the number of nodes (Count), the starting index (Start) 43 | # or the template used to construct the name (Hostname). 44 | # 45 | # Note: Template and Specs are not mutually exclusive. You may define both 46 | # sections and the aggregate nodes will be created for you. Take care with 47 | # name collisions 48 | # --------------------------------------------------------------------------- 49 | Template: 50 | Count: 1 51 | SANS: 52 | - localhost 53 | # Start: 5 54 | # Hostname: {{.Prefix}}{{.Index}} # default 55 | # --------------------------------------------------------------------------- 56 | # "Users" 57 | # --------------------------------------------------------------------------- 58 | # Count: The number of user accounts _in addition_ to Admin 59 | # --------------------------------------------------------------------------- 60 | Users: 61 | Count: 1 62 | -------------------------------------------------------------------------------- /fabric/scripts/configUpdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | # import utils 9 | . scripts/envVar.sh 10 | 11 | # fetchChannelConfig 12 | # Writes the current channel config for a given channel to a JSON file 13 | # NOTE: this must be run in a CLI container since it requires configtxlator 14 | fetchChannelConfig() { 15 | ORG=$1 16 | CHANNEL=$2 17 | OUTPUT=$3 18 | 19 | setGlobals $ORG 20 | 21 | infoln "Fetching the most recent configuration block for the channel" 22 | set -x 23 | peer channel fetch config config_block.pb -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL --tls --cafile $ORDERER_CA 24 | { set +x; } 2>/dev/null 25 | 26 | infoln "Decoding config block to JSON and isolating config to ${OUTPUT}" 27 | set -x 28 | configtxlator proto_decode --input config_block.pb --type common.Block | jq .data.data[0].payload.data.config >"${OUTPUT}" 29 | { set +x; } 2>/dev/null 30 | } 31 | 32 | # createConfigUpdate 33 | # Takes an original and modified config, and produces the config update tx 34 | # which transitions between the two 35 | # NOTE: this must be run in a CLI container since it requires configtxlator 36 | createConfigUpdate() { 37 | CHANNEL=$1 38 | ORIGINAL=$2 39 | MODIFIED=$3 40 | OUTPUT=$4 41 | 42 | set -x 43 | configtxlator proto_encode --input "${ORIGINAL}" --type common.Config >original_config.pb 44 | configtxlator proto_encode --input "${MODIFIED}" --type common.Config >modified_config.pb 45 | configtxlator compute_update --channel_id "${CHANNEL}" --original original_config.pb --updated modified_config.pb >config_update.pb 46 | configtxlator proto_decode --input config_update.pb --type common.ConfigUpdate >config_update.json 47 | echo '{"payload":{"header":{"channel_header":{"channel_id":"'$CHANNEL'", "type":2}},"data":{"config_update":'$(cat config_update.json)'}}}' | jq . >config_update_in_envelope.json 48 | configtxlator proto_encode --input config_update_in_envelope.json --type common.Envelope >"${OUTPUT}" 49 | { set +x; } 2>/dev/null 50 | } 51 | 52 | # signConfigtxAsPeerOrg 53 | # Set the peerOrg admin of an org and sign the config update 54 | signConfigtxAsPeerOrg() { 55 | ORG=$1 56 | CONFIGTXFILE=$2 57 | setGlobals $ORG 58 | set -x 59 | peer channel signconfigtx -f "${CONFIGTXFILE}" 60 | { set +x; } 2>/dev/null 61 | } 62 | -------------------------------------------------------------------------------- /fabric/scripts/createChannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # imports 4 | . scripts/envVar.sh 5 | . scripts/utils.sh 6 | 7 | CHANNEL_NAME="$1" 8 | DELAY="$2" 9 | MAX_RETRY="$3" 10 | VERBOSE="$4" 11 | ORG_QNTY=$5 12 | : ${CHANNEL_NAME:="mychannel"} 13 | : ${DELAY:="3"} 14 | : ${MAX_RETRY:="5"} 15 | : ${VERBOSE:="false"} 16 | : ${ORG_QNTY:=3} 17 | 18 | if [ ! -d "channel-artifacts" ]; then 19 | mkdir channel-artifacts 20 | fi 21 | 22 | createChannelTx() { 23 | set -x 24 | if [ $ORG_QNTY -gt 1 ] 25 | then 26 | configtxgen -configPath "./configtx" -profile TwoOrgsChannel -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME 27 | else 28 | configtxgen -configPath "./configtx-1org" -profile OneOrgChannel -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME 29 | fi 30 | res=$? 31 | { set +x; } 2>/dev/null 32 | verifyResult $res "Failed to generate channel configuration transaction..." 33 | } 34 | 35 | createChannel() { 36 | if [ $ORG_QNTY -gt 1 ] 37 | then 38 | setGlobals 1 39 | else 40 | setGlobals 0 41 | fi 42 | # Poll in case the raft leader is not set yet 43 | local rc=1 44 | local COUNTER=1 45 | while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do 46 | sleep $DELAY 47 | set -x 48 | osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7052 --ca-file "$ORDERER_CA" --client-cert $ORDERER_ADMIN_TLS_SIGN_CERT --client-key $ORDERER_ADMIN_TLS_PRIVATE_KEY>&log.txt 49 | res=$? 50 | { set +x; } 2>/dev/null 51 | let rc=$res 52 | COUNTER=$(expr $COUNTER + 1) 53 | done 54 | cat log.txt 55 | verifyResult $res "Channel creation failed" 56 | } 57 | 58 | # joinChannel ORG 59 | joinChannel() { 60 | FABRIC_CFG_PATH=$PWD/./config/ 61 | ORG=$1 62 | setGlobals $ORG 63 | local rc=1 64 | local COUNTER=1 65 | ## Sometimes Join takes time, hence retry 66 | while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do 67 | sleep $DELAY 68 | set -x 69 | peer channel join -b $BLOCKFILE >&log.txt 70 | res=$? 71 | { set +x; } 2>/dev/null 72 | let rc=$res 73 | COUNTER=$(expr $COUNTER + 1) 74 | done 75 | cat log.txt 76 | verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' " 77 | } 78 | 79 | setAnchorPeer() { 80 | ORG=$1 81 | docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME 82 | } 83 | 84 | if [ $ORG_QNTY -gt 1 ] 85 | then 86 | FABRIC_CFG_PATH=${PWD}/configtx 87 | 88 | ## Create channeltx 89 | infoln "Generating channel create transaction '${CHANNEL_NAME}.tx'" 90 | createChannelTx 91 | 92 | FABRIC_CFG_PATH=$PWD/./config/ 93 | BLOCKFILE="./channel-artifacts/${CHANNEL_NAME}.block" 94 | 95 | ## Create channel 96 | infoln "Creating channel ${CHANNEL_NAME}" 97 | createChannel 98 | successln "Channel '$CHANNEL_NAME' created" 99 | 100 | ## Join all the peers to the channel 101 | infoln "Joining org1 peer to the channel..." 102 | joinChannel 1 103 | infoln "Joining org2 peer to the channel..." 104 | joinChannel 2 105 | infoln "Joining org3 peer to the channel..." 106 | joinChannel 3 107 | 108 | ## Set the anchor peers for each org in the channel 109 | infoln "Setting anchor peer for org1..." 110 | setAnchorPeer 1 111 | infoln "Setting anchor peer for org2..." 112 | setAnchorPeer 2 113 | infoln "Setting anchor peer for org2..." 114 | setAnchorPeer 3 115 | else 116 | FABRIC_CFG_PATH=${PWD}/configtx-1org 117 | 118 | ## Create channeltx 119 | infoln "Generating channel create transaction '${CHANNEL_NAME}.tx'" 120 | createChannelTx 121 | 122 | FABRIC_CFG_PATH=$PWD/./config/ 123 | BLOCKFILE="./channel-artifacts/${CHANNEL_NAME}.block" 124 | 125 | ## Create channel 126 | infoln "Creating channel ${CHANNEL_NAME}" 127 | createChannel 128 | successln "Channel '$CHANNEL_NAME' created" 129 | 130 | ## Join all the peers to the channel 131 | infoln "Joining org peer to the channel..." 132 | joinChannel 0 133 | 134 | ## Set the anchor peers for each org in the channel 135 | infoln "Setting anchor peer for org..." 136 | setAnchorPeer 0 137 | fi 138 | 139 | successln "Channel '$CHANNEL_NAME' joined" 140 | -------------------------------------------------------------------------------- /fabric/scripts/envVar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp All Rights Reserved 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | # This is a collection of bash functions used by different scripts 9 | 10 | # imports 11 | . scripts/utils.sh 12 | 13 | export CORE_PEER_TLS_ENABLED=true 14 | export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem 15 | export PEER0_ORG_CA=${PWD}/organizations/peerOrganizations/org.example.com/peers/peer0.org.example.com/tls/ca.crt 16 | export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 17 | export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt 18 | export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt 19 | export ORDERER_ADMIN_TLS_SIGN_CERT=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt 20 | export ORDERER_ADMIN_TLS_PRIVATE_KEY=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key 21 | 22 | # Set environment variables for the peer org 23 | setGlobals() { 24 | local USING_ORG="" 25 | if [ -z "$OVERRIDE_ORG" ]; then 26 | USING_ORG=$1 27 | else 28 | USING_ORG="${OVERRIDE_ORG}" 29 | fi 30 | infoln "Using organization ${USING_ORG}" 31 | if [ $USING_ORG -eq 1 ]; then 32 | export CORE_PEER_LOCALMSPID="org1MSP" 33 | export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA 34 | export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp 35 | export CORE_PEER_ADDRESS=localhost:7051 36 | export CCAAS_CERTS_PATH=${PWD}/organizations/ccaas/org1.example.com 37 | elif [ $USING_ORG -eq 2 ]; then 38 | export CORE_PEER_LOCALMSPID="org2MSP" 39 | export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA 40 | export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp 41 | export CORE_PEER_ADDRESS=localhost:8051 42 | export CCAAS_CERTS_PATH=${PWD}/organizations/ccaas/org2.example.com 43 | elif [ $USING_ORG -eq 3 ]; then 44 | export CORE_PEER_LOCALMSPID="org3MSP" 45 | export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG3_CA 46 | export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp 47 | export CORE_PEER_ADDRESS=localhost:9051 48 | export CCAAS_CERTS_PATH=${PWD}/organizations/ccaas/org3.example.com 49 | elif [ $USING_ORG -eq 0 ]; then 50 | export CORE_PEER_LOCALMSPID="orgMSP" 51 | export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG_CA 52 | export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org.example.com/users/Admin@org.example.com/msp 53 | export CORE_PEER_ADDRESS=localhost:7051 54 | export CCAAS_CERTS_PATH=${PWD}/organizations/ccaas/org.example.com 55 | else 56 | errorln "ORG Unknown" 57 | fi 58 | 59 | if [ "$VERBOSE" == "true" ]; then 60 | env | grep CORE 61 | fi 62 | } 63 | 64 | # Set environment variables for use in the CLI container 65 | setGlobalsCLI() { 66 | setGlobals $1 67 | 68 | local USING_ORG="" 69 | if [ -z "$OVERRIDE_ORG" ]; then 70 | USING_ORG=$1 71 | else 72 | USING_ORG="${OVERRIDE_ORG}" 73 | fi 74 | if [ $USING_ORG -eq 1 ]; then 75 | export CORE_PEER_ADDRESS=peer0.org1.example.com:7051 76 | elif [ $USING_ORG -eq 2 ]; then 77 | export CORE_PEER_ADDRESS=peer0.org2.example.com:8051 78 | elif [ $USING_ORG -eq 3 ]; then 79 | export CORE_PEER_ADDRESS=peer0.org3.example.com:9051 80 | elif [ $USING_ORG -eq 0 ]; then 81 | export CORE_PEER_ADDRESS=peer0.org.example.com:7051 82 | else 83 | errorln "ORG Unknown" 84 | fi 85 | } 86 | 87 | # parsePeerConnectionParameters $@ 88 | # Helper function that sets the peer connection parameters for a chaincode 89 | # operation 90 | parsePeerConnectionParameters() { 91 | PEER_CONN_PARMS="" 92 | PEERS="" 93 | while [ "$#" -gt 0 ]; do 94 | if [ "$1" != 0 ] 95 | then 96 | setGlobals $1 97 | PEER="peer0.org$1" 98 | ## Set peer addresses 99 | PEERS="$PEERS $PEER" 100 | PEER_CONN_PARMS="$PEER_CONN_PARMS --peerAddresses $CORE_PEER_ADDRESS" 101 | ## Set path to TLS certificate 102 | TLSINFO=$(eval echo "--tlsRootCertFiles \$PEER0_ORG$1_CA") 103 | PEER_CONN_PARMS="$PEER_CONN_PARMS $TLSINFO" 104 | else 105 | setGlobals 0 106 | PEER="peer0.org" 107 | ## Set peer addresses 108 | PEERS="$PEERS $PEER" 109 | PEER_CONN_PARMS="$PEER_CONN_PARMS --peerAddresses $CORE_PEER_ADDRESS" 110 | ## Set path to TLS certificate 111 | TLSINFO=$(eval echo "--tlsRootCertFiles \$PEER0_ORG_CA") 112 | PEER_CONN_PARMS="$PEER_CONN_PARMS $TLSINFO" 113 | fi 114 | # shift by one to get to the next organization 115 | shift 116 | done 117 | # remove leading space for output 118 | PEERS="$(echo -e "$PEERS" | sed -e 's/^[[:space:]]*//')" 119 | } 120 | -------------------------------------------------------------------------------- /fabric/scripts/org3-scripts/joinChannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | # This script is designed to be run in the cli container as the 9 | # second step of the EYFN tutorial. It joins the org3 peers to the 10 | # channel previously setup in the BYFN tutorial and install the 11 | # chaincode as version 2.0 on peer0.org3. 12 | # 13 | 14 | CHANNEL_NAME="$1" 15 | DELAY="$2" 16 | TIMEOUT="$3" 17 | VERBOSE="$4" 18 | : ${CHANNEL_NAME:="mychannel"} 19 | : ${DELAY:="3"} 20 | : ${TIMEOUT:="10"} 21 | : ${VERBOSE:="false"} 22 | COUNTER=1 23 | MAX_RETRY=5 24 | 25 | # import environment variables 26 | . scripts/envVar.sh 27 | 28 | # joinChannel ORG 29 | joinChannel() { 30 | ORG=$1 31 | local rc=1 32 | local COUNTER=1 33 | ## Sometimes Join takes time, hence retry 34 | while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do 35 | sleep $DELAY 36 | set -x 37 | peer channel join -b $BLOCKFILE >&log.txt 38 | res=$? 39 | { set +x; } 2>/dev/null 40 | let rc=$res 41 | COUNTER=$(expr $COUNTER + 1) 42 | done 43 | cat log.txt 44 | verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' " 45 | } 46 | 47 | setAnchorPeer() { 48 | ORG=$1 49 | scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME 50 | } 51 | 52 | setGlobalsCLI 3 53 | BLOCKFILE="${CHANNEL_NAME}.block" 54 | 55 | echo "Fetching channel config block from orderer..." 56 | set -x 57 | peer channel fetch 0 $BLOCKFILE -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME --tls --cafile $ORDERER_CA >&log.txt 58 | res=$? 59 | { set +x; } 2>/dev/null 60 | cat log.txt 61 | verifyResult $res "Fetching config block from orderer has failed" 62 | 63 | infoln "Joining org3 peer to the channel..." 64 | joinChannel 3 65 | 66 | infoln "Setting anchor peer for org3..." 67 | setAnchorPeer 3 68 | 69 | successln "Channel '$CHANNEL_NAME' joined" 70 | successln "Org3 peer successfully added to network" 71 | -------------------------------------------------------------------------------- /fabric/scripts/org3-scripts/updateChannelConfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | # This script is designed to be run in the cli container as the 9 | # first step of the EYFN tutorial. It creates and submits a 10 | # configuration transaction to add org3 to the test network 11 | # 12 | 13 | CHANNEL_NAME="$1" 14 | DELAY="$2" 15 | TIMEOUT="$3" 16 | VERBOSE="$4" 17 | : ${CHANNEL_NAME:="mychannel"} 18 | : ${DELAY:="3"} 19 | : ${TIMEOUT:="10"} 20 | : ${VERBOSE:="false"} 21 | COUNTER=1 22 | MAX_RETRY=5 23 | 24 | 25 | # imports 26 | . scripts/envVar.sh 27 | . scripts/configUpdate.sh 28 | . scripts/utils.sh 29 | 30 | infoln "Creating config transaction to add org3 to network" 31 | 32 | # Fetch the config for the channel, writing it to config.json 33 | fetchChannelConfig 1 ${CHANNEL_NAME} config.json 34 | 35 | # Modify the configuration to append the new org 36 | set -x 37 | jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"org3MSP":.[1]}}}}}' config.json ./organizations/peerOrganizations/org3.example.com/org3.json > modified_config.json 38 | { set +x; } 2>/dev/null 39 | 40 | # Compute a config update, based on the differences between config.json and modified_config.json, write it as a transaction to org3_update_in_envelope.pb 41 | createConfigUpdate ${CHANNEL_NAME} config.json modified_config.json org3_update_in_envelope.pb 42 | 43 | infoln "Signing config transaction" 44 | signConfigtxAsPeerOrg 1 org3_update_in_envelope.pb 45 | 46 | infoln "Submitting transaction from a different peer (peer0.org2) which also signs it" 47 | setGlobals 2 48 | set -x 49 | peer channel update -f org3_update_in_envelope.pb -c ${CHANNEL_NAME} -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${ORDERER_CA} 50 | { set +x; } 2>/dev/null 51 | 52 | successln "Config transaction to add org3 to network submitted" 53 | -------------------------------------------------------------------------------- /fabric/scripts/setAnchorPeer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | # import utils 9 | . scripts/envVar.sh 10 | . scripts/configUpdate.sh 11 | 12 | 13 | # NOTE: this must be run in a CLI container since it requires jq and configtxlator 14 | createAnchorPeerUpdate() { 15 | infoln "Fetching channel config for channel $CHANNEL_NAME" 16 | fetchChannelConfig $ORG $CHANNEL_NAME ${CORE_PEER_LOCALMSPID}config.json 17 | 18 | infoln "Generating anchor peer update transaction for Org${ORG} on channel $CHANNEL_NAME" 19 | 20 | if [ $ORG -eq 1 ]; then 21 | HOST="peer0.org1.example.com" 22 | PORT=7051 23 | elif [ $ORG -eq 2 ]; then 24 | HOST="peer0.org2.example.com" 25 | PORT=7051 26 | elif [ $ORG -eq 3 ]; then 27 | HOST="peer0.org3.example.com" 28 | PORT=7051 29 | elif [ $ORG -eq 0 ]; then 30 | HOST="peer0.org.example.com" 31 | PORT=7051 32 | else 33 | errorln "org${ORG} unknown" 34 | fi 35 | 36 | set -x 37 | # Modify the configuration to append the anchor peer 38 | jq '.channel_group.groups.Application.groups.'${CORE_PEER_LOCALMSPID}'.values += {"AnchorPeers":{"mod_policy": "Admins","value":{"anchor_peers": [{"host": "'$HOST'","port": '$PORT'}]},"version": "0"}}' ${CORE_PEER_LOCALMSPID}config.json > ${CORE_PEER_LOCALMSPID}modified_config.json 39 | { set +x; } 2>/dev/null 40 | 41 | # Compute a config update, based on the differences between 42 | # {orgmsp}config.json and {orgmsp}modified_config.json, write 43 | # it as a transaction to {orgmsp}anchors.tx 44 | createConfigUpdate ${CHANNEL_NAME} ${CORE_PEER_LOCALMSPID}config.json ${CORE_PEER_LOCALMSPID}modified_config.json ${CORE_PEER_LOCALMSPID}anchors.tx 45 | } 46 | 47 | updateAnchorPeer() { 48 | peer channel update -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME -f ${CORE_PEER_LOCALMSPID}anchors.tx --tls --cafile $ORDERER_CA >&log.txt 49 | res=$? 50 | cat log.txt 51 | verifyResult $res "Anchor peer update failed" 52 | successln "Anchor peer set for org '$CORE_PEER_LOCALMSPID' on channel '$CHANNEL_NAME'" 53 | } 54 | 55 | ORG=$1 56 | CHANNEL_NAME=$2 57 | setGlobalsCLI $ORG 58 | 59 | createAnchorPeerUpdate 60 | 61 | updateAnchorPeer 62 | -------------------------------------------------------------------------------- /fabric/startDev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ORG_QNTY=3 4 | DEPLOY_CCAAS=false 5 | CCAAS_TLS_ENABLED="" 6 | 7 | while [[ $# -ge 1 ]] ; do 8 | key="$1" 9 | case $key in 10 | -n ) 11 | ORG_QNTY=$2 12 | shift 13 | ;; 14 | -ccaas ) 15 | DEPLOY_CCAAS=$2 16 | shift 17 | ;; 18 | -ccaastls ) 19 | CCAAS_TLS_ENABLED="-ccaastls" 20 | shift 21 | ;; 22 | esac 23 | shift 24 | done 25 | 26 | if [ $ORG_QNTY != 3 -a $ORG_QNTY != 1 ] 27 | then 28 | echo 'WARNING: The number of organizations allowed is either 3 or 1.' 29 | echo 'Defaulting to 3 organizations.' 30 | ORG_QNTY=3 31 | fi 32 | 33 | CCCG_PATH="../chaincode/collections.json" 34 | 35 | ./network.sh down -n $ORG_QNTY 36 | rm -rf organizations/peerOrganizations 37 | rm -rf organizations/ordererOrganizations 38 | rm -rf organizations/rest-certs 39 | 40 | download_binaries(){ 41 | echo "Preparing to download fabric binaries..." 42 | curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh && chmod +x install-fabric.sh 43 | 44 | echo "Downloading fabric binaries..." 45 | ./install-fabric.sh --fabric-version 2.5.3 binary 46 | 47 | rm install-fabric.sh 48 | } 49 | 50 | # Check PATH 51 | command_exists() { 52 | command -v "$1" >/dev/null 2>&1 53 | } 54 | 55 | # Binaries to check 56 | fabric_binaries=("fabric-ca-client" "fabric-ca-server" "osnadmin" "configtxgen" "configtxlator" "cryptogen" "discover" "orderer" "peer") 57 | 58 | all_binaries_exist() { 59 | for binary in "${fabric_binaries[@]}"; do 60 | if ! command_exists "$binary"; then 61 | return 1 62 | fi 63 | done 64 | return 0 65 | } 66 | 67 | if all_binaries_exist; then 68 | echo "All Fabric binaries are available in the system path." 69 | else 70 | echo "Some or all Fabric binaries are missing from the system path." 71 | 72 | FILE=bin 73 | if [ ! -d "$FILE" ]; then 74 | echo "Directory $FILE not found" 75 | download_binaries 76 | else 77 | echo "Bin directory already exists" 78 | cd bin 79 | numFiles="$(ls -1 | wc -l)" 80 | if [ "$numFiles" -ne 10 ]; then 81 | cd .. 82 | echo "Missing some fabric binaries in bin directory" 83 | download_binaries 84 | else 85 | cd .. 86 | fi 87 | fi 88 | fi 89 | 90 | docker network create cc-tools-demo-net 91 | ./network.sh up createChannel -n $ORG_QNTY $CCAAS_TLS_ENABLED 92 | 93 | if [ "$DEPLOY_CCAAS" = "false" ]; then 94 | ./network.sh deployCC -ccn cc-tools-demo -ccp ../chaincode -ccl go -n $ORG_QNTY -cccg $CCCG_PATH 95 | else 96 | ./network.sh deployCCAAS -ccn cc-tools-demo -ccp ../chaincode -n $ORG_QNTY -cccg $CCCG_PATH $CCAAS_TLS_ENABLED 97 | fi 98 | -------------------------------------------------------------------------------- /generatePackage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | # Default values for the flags 4 | FLAG_CCAPI="none" 5 | FLAG_LABEL="1.0" 6 | SKIP_COLL_GEN=false 7 | 8 | # You can change this if you want to avoid using the --name flag 9 | FLAG_NAME="cc-tools-demo" 10 | 11 | # Process command-line arguments 12 | while [[ $# -gt 0 ]]; do 13 | case $1 in 14 | --label | -l) 15 | if [[ $# -gt 1 ]]; then 16 | FLAG_LABEL=$2 17 | shift 2 18 | else 19 | echo "Error: --label flag requires a value." 20 | exit 1 21 | fi 22 | ;; 23 | --name | -n) 24 | if [[ $# -gt 1 ]]; then 25 | FLAG_NAME=$2 26 | shift 2 27 | else 28 | echo "Error: --name flag requires a value." 29 | exit 1 30 | fi 31 | ;; 32 | --org | -o) 33 | if [[ $# -gt 1 ]]; then 34 | orgs+=("$2") 35 | shift 2 36 | else 37 | echo "Error: --org flag requires a value." 38 | exit 1 39 | fi 40 | ;; 41 | --skip | -s) 42 | SKIP_COLL_GEN=true 43 | shift 1 44 | ;; 45 | --help | -h) 46 | echo "Usage: ./generateTar.sh [--label