├── .github ├── PULL_REQUEST_TEMPLATE.md ├── settings.yml └── workflows │ └── build.yaml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── SECURITY.md ├── ci ├── install-tools.sh ├── lint.sh └── tools │ ├── go.mod │ ├── go.sum │ └── tools.go ├── configtx ├── application.go ├── application_test.go ├── capabilities.go ├── channel.go ├── channel_test.go ├── config.go ├── config_test.go ├── consortiums.go ├── consortiums_test.go ├── constants.go ├── example_test.go ├── internal │ └── policydsl │ │ ├── policyparser.go │ │ └── policyparser_test.go ├── membership │ └── membership.go ├── msp.go ├── msp_test.go ├── orderer.go ├── orderer │ └── orderer.go ├── orderer_test.go ├── organization.go ├── organization_test.go ├── policies.go ├── policies_test.go ├── signer.go ├── signer_test.go ├── update.go └── update_test.go ├── go.mod ├── go.sum └── protolator ├── api.go ├── dynamic.go ├── dynamic_test.go ├── integration ├── integration_test.go └── testdata │ ├── block.json │ └── block.pb ├── json.go ├── json_test.go ├── nested.go ├── nested_test.go ├── protoext ├── commonext │ ├── common.go │ ├── common_test.go │ ├── commonext_test.go │ ├── configtx.go │ ├── configuration.go │ └── policies.go ├── decorate.go ├── decorate_test.go ├── ledger │ └── rwsetext │ │ ├── rwset.go │ │ └── rwsetext_test.go ├── mspext │ ├── msp_config.go │ ├── msp_principal.go │ └── mspext_test.go ├── ordererext │ ├── configuration.go │ └── ordererext_test.go └── peerext │ ├── configuration.go │ ├── peerext_test.go │ ├── proposal.go │ ├── proposal_response.go │ └── transaction.go ├── statically_opaque.go ├── statically_opaque_test.go ├── testprotos ├── sample.go ├── sample.pb.go └── sample.proto ├── variably_opaque.go └── variably_opaque_test.go /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #### Type of change 6 | 7 | 8 | 9 | - Bug fix 10 | - New feature 11 | - Improvement (improvement to code, performance, etc) 12 | - Test update 13 | - Documentation update 14 | 15 | #### Description 16 | 17 | 18 | 19 | #### Additional details 20 | 21 | 22 | 23 | 24 | #### Related issues 25 | 26 | 27 | 28 | 34 | 35 | 48 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | repository: 6 | name: fabric-config 7 | description: Hyperledger Fabric Packages for channel configuration transactions 8 | homepage: https://wiki.hyperledger.org/display/fabric 9 | default_branch: main 10 | has_downloads: false 11 | has_issues: false 12 | has_projects: false 13 | has_wiki: false 14 | archived: false 15 | private: false 16 | allow_squash_merge: true 17 | allow_merge_commit: false 18 | allow_rebase_merge: true 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | name: Verify Build 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | - release-* 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | 15 | env: 16 | GOPATH: /opt/go 17 | PATH: /opt/go/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin 18 | GO_VER: 1.21.9 19 | 20 | jobs: 21 | unit-tests: 22 | name: Unit Tests 23 | runs-on: ubuntu-20.04 24 | steps: 25 | - uses: actions/setup-go@v3 26 | name: Install Go 27 | with: 28 | go-version: ${{ env.GO_VER }} 29 | - uses: actions/checkout@v3 30 | name: Checkout Fabric Code 31 | - run: ci/install-tools.sh 32 | name: Install Tools 33 | - run: ci/lint.sh 34 | name: Vet and lint 35 | - run: go test -race ./... 36 | name: Run tests 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #SPDX-License-Identifier: Apache-2.0 2 | .#* 3 | *~ 4 | *# 5 | /bin 6 | /build 7 | /.build 8 | *.cov 9 | /docs/build/* 10 | .DS_Store 11 | .*-dummy 12 | .gradle 13 | .idea 14 | *.iml 15 | *.log 16 | .project 17 | /release 18 | report.xml 19 | results.xml 20 | .settings 21 | .*.sw* 22 | tags 23 | .tags 24 | TESTS*.xml 25 | .tox/ 26 | .vagrant/ 27 | .vscode 28 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # Fabric Maintainers 4 | * @hyperledger/fabric-core-maintainers 5 | /docs/ @hyperledger/fabric-core-doc-maintainers @hyperledger/fabric-core-maintainers -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Code of Conduct Guidelines 2 | ========================== 3 | 4 | Please review the Hyperledger [Code of Conduct](https://wiki.hyperledger.org/community/hyperledger-project-code-of-conduct) 5 | before participating. It is important that we keep things civil. 6 | 7 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | We welcome contributions to the Hyperledger Fabric Project in many forms, and there's always plenty to do! 4 | 5 | Please visit the [contributors guide](http://hyperledger-fabric.readthedocs.io/en/latest/CONTRIBUTING.html) in the docs to learn how to make contributions to this exciting project. 6 | 7 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. 8 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers 2 | 3 | See [MAINTAINERS.md](https://github.com/hyperledger/fabric/blob/main/MAINTAINERS.md) in the Fabric repository. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Fabric Packages for Fabric Config 2 | 3 | [![GoDoc](https://godoc.org/github.com/hyperledger/fabric-config?status.svg)](https://godoc.org/github.com/hyperledger/fabric-config) 4 | 5 | This repository contains the packages used by go implementations of the Fabric 6 | Config API. 7 | 8 | We welcome contributions to the Hyperledger Fabric project in many forms. 9 | There’s always plenty to do! Check the documentation on 10 | [how to contribute][contributing] to this project for the full details. 11 | 12 | ## Community 13 | 14 | - [Hyperledger Community](https://www.hyperledger.org/community) 15 | - [Hyperledger mailing lists and archives](http://lists.hyperledger.org/) 16 | - [Hyperledger Chat](http://chat.hyperledger.org/channel/fabric) 17 | - [Hyperledger Fabric Issue Tracking (JIRA)](https://jira.hyperledger.org/secure/Dashboard.jspa?selectPageId=10104) 18 | - [Hyperledger Fabric Wiki](https://wiki.hyperledger.org/display/Fabric) 19 | - [Hyperledger Wiki](https://wiki.hyperledger.org/) 20 | - [Hyperledger Code of Conduct](https://wiki.hyperledger.org/display/HYP/Hyperledger+Code+of+Conduct) 21 | 22 | ## License 23 | 24 | Hyperledger Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the [LICENSE](LICENSE) file. Hyperledger Project documentation files are made available under the Creative Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/. 25 | 26 | [contributing]: https://hyperledger-fabric.readthedocs.io/en/latest/CONTRIBUTING.html 27 | [grpc]: https://grpc.io/docs/guides/ 28 | [protobuf]: https://github.com/protocolbuffers/protobuf/ 29 | [rocketchat-image]: https://open.rocket.chat/images/join-chat.svg 30 | [rocketchat-url]: https://chat.hyperledger.org/channel/fabric 31 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Security Policy 2 | 3 | ## Reporting a Security Bug 4 | 5 | If you think you have discovered a security issue in any of the Hyperledger 6 | projects, we'd love to hear from you. We will take all security bugs 7 | seriously and if confirmed upon investigation we will patch it within a 8 | reasonable amount of time and release a public security bulletin discussing 9 | the impact and credit the discoverer. 10 | 11 | There are two ways to report a security bug. The easiest is to email a 12 | description of the flaw and any related information (e.g. reproduction 13 | steps, version) to 14 | [security at hyperledger dot org](mailto:security@hyperledger.org). 15 | 16 | The other way is to file a confidential security bug in our 17 | [JIRA bug tracking system](https://jira.hyperledger.org). 18 | Be sure to set the “Security Level” to “Security issue”. 19 | 20 | The process by which the Hyperledger Security Team handles security bugs 21 | is documented further in our 22 | [Defect Response](https://wiki.hyperledger.org/display/HYP/Defect+Response) 23 | page on our [wiki](https://wiki.hyperledger.org). 24 | -------------------------------------------------------------------------------- /ci/install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright the Hyperledger Fabric contributors. All rights reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -euo pipefail 8 | 9 | cd "$(dirname "$0")/tools" 10 | export GO111MODULE=on 11 | go install -tags tools golang.org/x/lint/golint 12 | go install -tags tools golang.org/x/tools/cmd/goimports 13 | go install -tags tools mvdan.cc/gofumpt 14 | -------------------------------------------------------------------------------- /ci/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright the Hyperledger Fabric contributors. All rights reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -euo pipefail 8 | 9 | go_files=() 10 | while IFS=$'\n' read -r filename; do 11 | go_files+=("$filename") 12 | done < <(find . -type f -name '*.go'| grep -v '.pb.go$') 13 | 14 | ## Import management 15 | echo "running goimports..." 16 | goimports_output="$(goimports -l "${go_files[@]}")" 17 | if [ -n "$goimports_output" ]; then 18 | echo "The following files contain goimport errors:" 19 | echo "$goimports_output" 20 | echo "Please run 'goimports -l -w' for these files." 21 | exit 1 22 | fi 23 | 24 | ## Formatting 25 | echo "running gofumpt..." 26 | gofumpt_output="$(gofumpt -l -s "${go_files[@]}")" 27 | if [ -n "$gofumpt_output" ]; then 28 | echo "The following files contain gofumpt errors:" 29 | echo "$gofumpt_output" 30 | echo "Please run 'gofumpt -s -w' for these files." 31 | exit 1 32 | fi 33 | 34 | ## go vet 35 | echo "running go vet..." 36 | go vet ./... 37 | 38 | ## golint 39 | # TODO also lint protolator 40 | echo "running golint..." 41 | go list ./... | grep -v 'protolator' | xargs -d '\n' golint -set_exit_status 42 | 43 | ## Protobuf decoration 44 | # TODO verify protolator decorates all config protobuf messages 45 | -------------------------------------------------------------------------------- /ci/tools/go.mod: -------------------------------------------------------------------------------- 1 | module tools 2 | 3 | go 1.14 4 | 5 | require ( 6 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b 7 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c 8 | mvdan.cc/gofumpt v0.1.0 9 | ) 10 | -------------------------------------------------------------------------------- /ci/tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 2 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 4 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 5 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 6 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= 9 | github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 10 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 11 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 12 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 13 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 14 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 15 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 16 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 17 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 18 | golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= 19 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 20 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 21 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 22 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 29 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 30 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 31 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 32 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 33 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c h1:dS09fXwOFF9cXBnIzZexIuUBj95U1NyQjkEhkgidDow= 34 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 39 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 42 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= 44 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 45 | mvdan.cc/gofumpt v0.1.0 h1:hsVv+Y9UsZ/mFZTxJZuHVI6shSQCtzZ11h1JEFPAZLw= 46 | mvdan.cc/gofumpt v0.1.0/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= 47 | -------------------------------------------------------------------------------- /ci/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // Copyright the Hyperledger Fabric contributors. All rights reserved. 5 | // 6 | // SPDX-License-Identifier: Apache-2.0 7 | 8 | package tools 9 | 10 | import ( 11 | _ "golang.org/x/lint/golint" 12 | _ "golang.org/x/tools/cmd/goimports" 13 | _ "mvdan.cc/gofumpt" 14 | ) 15 | -------------------------------------------------------------------------------- /configtx/capabilities.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | 13 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | // capabilitiesValue returns the config definition for a set of capabilities. 18 | // It is a value for the /Channel/Orderer, Channel/Application/, and /Channel groups. 19 | func capabilitiesValue(capabilities []string) *standardConfigValue { 20 | c := &cb.Capabilities{ 21 | Capabilities: make(map[string]*cb.Capability), 22 | } 23 | 24 | for _, capability := range capabilities { 25 | c.Capabilities[capability] = &cb.Capability{} 26 | } 27 | 28 | return &standardConfigValue{ 29 | key: CapabilitiesKey, 30 | value: c, 31 | } 32 | } 33 | 34 | func addCapability(configGroup *cb.ConfigGroup, capabilities []string, modPolicy string, capability string) error { 35 | for _, c := range capabilities { 36 | if c == capability { 37 | // if capability already exist, do nothing. 38 | return nil 39 | } 40 | } 41 | capabilities = append(capabilities, capability) 42 | 43 | err := setValue(configGroup, capabilitiesValue(capabilities), modPolicy) 44 | if err != nil { 45 | return fmt.Errorf("adding capability: %v", err) 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func removeCapability(configGroup *cb.ConfigGroup, capabilities []string, modPolicy string, capability string) error { 52 | var updatedCapabilities []string 53 | 54 | for _, c := range capabilities { 55 | if c != capability { 56 | updatedCapabilities = append(updatedCapabilities, c) 57 | } 58 | } 59 | 60 | if len(updatedCapabilities) == len(capabilities) { 61 | return errors.New("capability not set") 62 | } 63 | 64 | err := setValue(configGroup, capabilitiesValue(updatedCapabilities), modPolicy) 65 | if err != nil { 66 | return fmt.Errorf("removing capability: %v", err) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func getCapabilities(configGroup *cb.ConfigGroup) ([]string, error) { 73 | capabilitiesValue, ok := configGroup.Values[CapabilitiesKey] 74 | if !ok { 75 | // no capabilities defined/enabled 76 | return nil, nil 77 | } 78 | 79 | capabilitiesProto := &cb.Capabilities{} 80 | 81 | err := proto.Unmarshal(capabilitiesValue.Value, capabilitiesProto) 82 | if err != nil { 83 | return nil, fmt.Errorf("unmarshaling capabilities: %v", err) 84 | } 85 | 86 | capabilities := []string{} 87 | 88 | for capability := range capabilitiesProto.Capabilities { 89 | capabilities = append(capabilities, capability) 90 | } 91 | 92 | return capabilities, nil 93 | } 94 | -------------------------------------------------------------------------------- /configtx/channel.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | 13 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 14 | ) 15 | 16 | // ChannelGroup encapsulates the parts of the config that control channels. 17 | // This type implements retrieval of the various channel config values. 18 | type ChannelGroup struct { 19 | channelGroup *cb.ConfigGroup 20 | } 21 | 22 | // Channel returns the channel group from the updated config. 23 | func (c *ConfigTx) Channel() *ChannelGroup { 24 | return &ChannelGroup{channelGroup: c.updated.ChannelGroup} 25 | } 26 | 27 | // Configuration returns a channel configuration value from a config transaction. 28 | func (c *ChannelGroup) Configuration() (Channel, error) { 29 | var ( 30 | config Channel 31 | err error 32 | ) 33 | 34 | if _, ok := c.channelGroup.Values[ConsortiumKey]; ok { 35 | consortiumProto := &cb.Consortium{} 36 | err := unmarshalConfigValueAtKey(c.channelGroup, ConsortiumKey, consortiumProto) 37 | if err != nil { 38 | return Channel{}, err 39 | } 40 | config.Consortium = consortiumProto.Name 41 | } 42 | 43 | if applicationGroup, ok := c.channelGroup.Groups[ApplicationGroupKey]; ok { 44 | a := &ApplicationGroup{applicationGroup: applicationGroup} 45 | config.Application, err = a.Configuration() 46 | if err != nil { 47 | return Channel{}, err 48 | } 49 | } 50 | 51 | if ordererGroup, ok := c.channelGroup.Groups[OrdererGroupKey]; ok { 52 | o := &OrdererGroup{ordererGroup: ordererGroup, channelGroup: c.channelGroup} 53 | config.Orderer, err = o.Configuration() 54 | if err != nil { 55 | return Channel{}, err 56 | } 57 | } 58 | 59 | if consortiumsGroup, ok := c.channelGroup.Groups[ConsortiumsGroupKey]; ok { 60 | c := &ConsortiumsGroup{consortiumsGroup: consortiumsGroup} 61 | config.Consortiums, err = c.Configuration() 62 | if err != nil { 63 | return Channel{}, err 64 | } 65 | } 66 | 67 | if _, ok := c.channelGroup.Values[CapabilitiesKey]; ok { 68 | config.Capabilities, err = c.Capabilities() 69 | if err != nil { 70 | return Channel{}, err 71 | } 72 | } 73 | 74 | config.Policies, err = c.Policies() 75 | if err != nil { 76 | return Channel{}, err 77 | } 78 | 79 | return config, nil 80 | } 81 | 82 | // Policies returns a map of policies for channel configuration. 83 | func (c *ChannelGroup) Policies() (map[string]Policy, error) { 84 | return getPolicies(c.channelGroup.Policies) 85 | } 86 | 87 | // SetModPolicy sets the specified modification policy for the channel group. 88 | func (c *ChannelGroup) SetModPolicy(modPolicy string) error { 89 | if modPolicy == "" { 90 | return errors.New("non empty mod policy is required") 91 | } 92 | 93 | c.channelGroup.ModPolicy = modPolicy 94 | 95 | return nil 96 | } 97 | 98 | // SetPolicy sets the specified policy in the channel group's config policy map. 99 | // If the policy already exists in current configuration, its value will be overwritten. 100 | func (c *ChannelGroup) SetPolicy(policyName string, policy Policy) error { 101 | return setPolicy(c.channelGroup, policyName, policy) 102 | } 103 | 104 | // SetPolicies sets the specified policies in the channel group's config policy map. 105 | // If the policies already exist in current configuration, the values will be replaced with new policies. 106 | func (c *ChannelGroup) SetPolicies(policies map[string]Policy) error { 107 | return setPolicies(c.channelGroup, policies) 108 | } 109 | 110 | // RemovePolicy removes an existing channel level policy. 111 | func (c *ChannelGroup) RemovePolicy(policyName string) error { 112 | policies, err := c.Policies() 113 | if err != nil { 114 | return err 115 | } 116 | 117 | removePolicy(c.channelGroup, policyName, policies) 118 | return nil 119 | } 120 | 121 | // Capabilities returns a map of enabled channel capabilities 122 | // from a config transaction's updated config. 123 | func (c *ChannelGroup) Capabilities() ([]string, error) { 124 | capabilities, err := getCapabilities(c.channelGroup) 125 | if err != nil { 126 | return nil, fmt.Errorf("retrieving channel capabilities: %v", err) 127 | } 128 | 129 | return capabilities, nil 130 | } 131 | 132 | // AddCapability adds capability to the provided channel config. 133 | // If the provided capability already exists in current configuration, this action 134 | // will be a no-op. 135 | func (c *ChannelGroup) AddCapability(capability string) error { 136 | capabilities, err := c.Capabilities() 137 | if err != nil { 138 | return err 139 | } 140 | 141 | err = addCapability(c.channelGroup, capabilities, AdminsPolicyKey, capability) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | 149 | // RemoveCapability removes capability to the provided channel config. 150 | func (c *ChannelGroup) RemoveCapability(capability string) error { 151 | capabilities, err := c.Capabilities() 152 | if err != nil { 153 | return err 154 | } 155 | 156 | err = removeCapability(c.channelGroup, capabilities, AdminsPolicyKey, capability) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | return nil 162 | } 163 | 164 | // RemoveLegacyOrdererAddresses removes the deprecated top level orderer addresses config key and value 165 | // from the channel config. 166 | // In fabric 1.4, top level orderer addresses were migrated to the org level orderer endpoints 167 | // While top-level orderer addresses are still supported, the organization value is preferred. 168 | func (c *ChannelGroup) RemoveLegacyOrdererAddresses() { 169 | delete(c.channelGroup.Values, OrdererAddressesKey) 170 | } 171 | -------------------------------------------------------------------------------- /configtx/constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | const ( 10 | // These values are fixed for the genesis block. 11 | msgVersion = 0 12 | epoch = 0 13 | 14 | // ConsortiumKey is the key for the ConfigValue of a 15 | // Consortium. 16 | ConsortiumKey = "Consortium" 17 | 18 | // HashingAlgorithmKey is the key for the ConfigValue of a 19 | // HashingAlgorithm. 20 | HashingAlgorithmKey = "HashingAlgorithm" 21 | 22 | // BlockDataHashingStructureKey is the key for the ConfigValue 23 | // of a BlockDataHashingStructure. 24 | BlockDataHashingStructureKey = "BlockDataHashingStructure" 25 | 26 | // CapabilitiesKey is the key for the ConfigValue, capabilities. 27 | // CapabiltiesKey can be used at the channel, application, and orderer levels. 28 | CapabilitiesKey = "Capabilities" 29 | 30 | // EndpointsKey is the key for the ConfigValue, Endpoints in 31 | // a OrdererOrgGroup. 32 | EndpointsKey = "Endpoints" 33 | 34 | // MSPKey is the key for the ConfigValue, MSP. 35 | MSPKey = "MSP" 36 | 37 | // AdminsPolicyKey is the key used for the admin policy. 38 | AdminsPolicyKey = "Admins" 39 | 40 | // ReadersPolicyKey is the key used for the read policy. 41 | ReadersPolicyKey = "Readers" 42 | 43 | // WritersPolicyKey is the key used for the write policy. 44 | WritersPolicyKey = "Writers" 45 | 46 | // EndorsementPolicyKey is the key used for the endorsement policy. 47 | EndorsementPolicyKey = "Endorsement" 48 | 49 | // LifecycleEndorsementPolicyKey is the key used for the lifecycle endorsement 50 | // policy. 51 | LifecycleEndorsementPolicyKey = "LifecycleEndorsement" 52 | 53 | // BlockValidationPolicyKey is the key used for the block validation policy in 54 | // the OrdererOrgGroup. 55 | BlockValidationPolicyKey = "BlockValidation" 56 | 57 | // ChannelCreationPolicyKey is the key used in the consortium config to denote 58 | // the policy to be used in evaluating whether a channel creation request 59 | // is authorized. 60 | ChannelCreationPolicyKey = "ChannelCreationPolicy" 61 | 62 | // ChannelGroupKey is the group name for the channel config. 63 | ChannelGroupKey = "Channel" 64 | 65 | // ConsortiumsGroupKey is the group name for the consortiums config. 66 | ConsortiumsGroupKey = "Consortiums" 67 | 68 | // OrdererGroupKey is the group name for the orderer config. 69 | OrdererGroupKey = "Orderer" 70 | 71 | // ApplicationGroupKey is the group name for the Application config. 72 | ApplicationGroupKey = "Application" 73 | 74 | // ACLsKey is the name of the ACLs config. 75 | ACLsKey = "ACLs" 76 | 77 | // AnchorPeersKey is the key name for the AnchorPeers ConfigValue. 78 | AnchorPeersKey = "AnchorPeers" 79 | 80 | // ImplicitMetaPolicyType is the 'Type' string for implicit meta policies. 81 | ImplicitMetaPolicyType = "ImplicitMeta" 82 | 83 | // SignaturePolicyType is the 'Type' string for signature policies. 84 | SignaturePolicyType = "Signature" 85 | 86 | ordererAdminsPolicyName = "/Channel/Orderer/Admins" 87 | 88 | // OrdererAddressesKey is the key for the ConfigValue of OrdererAddresses. 89 | OrdererAddressesKey = "OrdererAddresses" 90 | ) 91 | -------------------------------------------------------------------------------- /configtx/membership/membership.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package membership 8 | 9 | import ( 10 | "crypto" 11 | "crypto/x509" 12 | ) 13 | 14 | // KeyInfo represents a (secret) key that is either already stored 15 | // in the bccsp/keystore or key material to be imported to the 16 | // bccsp key-store. In later versions it may contain also a 17 | // keystore identifier. 18 | type KeyInfo struct { 19 | // Identifier of the key inside the default keystore; this for 20 | // the case of Software BCCSP as well as the HSM BCCSP would be 21 | // the SKI of the key. 22 | KeyIdentifier string 23 | // KeyMaterial (optional) for the key to be imported; this 24 | // must be a supported PKCS#8 private key type of either 25 | // *rsa.PrivateKey, *ecdsa.PrivateKey, or ed25519.PrivateKey. 26 | KeyMaterial crypto.PrivateKey 27 | } 28 | 29 | // SigningIdentityInfo represents the configuration information 30 | // related to the signing identity the peer is to use for generating 31 | // endorsements. 32 | type SigningIdentityInfo struct { 33 | // PublicSigner carries the public information of the signing 34 | // identity. For an X.509 provider this would be represented by 35 | // an X.509 certificate. 36 | PublicSigner *x509.Certificate 37 | // PrivateSigner denotes a reference to the private key of the 38 | // peer's signing identity. 39 | PrivateSigner KeyInfo 40 | } 41 | 42 | // CryptoConfig contains configuration parameters 43 | // for the cryptographic algorithms used by the MSP 44 | // this configuration refers to. 45 | type CryptoConfig struct { 46 | // SignatureHashFamily is a string representing the hash family to be used 47 | // during sign and verify operations. 48 | // Allowed values are "SHA2" and "SHA3". 49 | SignatureHashFamily string 50 | // IdentityIdentifierHashFunction is a string representing the hash function 51 | // to be used during the computation of the identity identifier of an MSP identity. 52 | // Allowed values are "SHA256", "SHA384" and "SHA3_256", "SHA3_384". 53 | IdentityIdentifierHashFunction string 54 | } 55 | 56 | // OUIdentifier represents an organizational unit and 57 | // its related chain of trust identifier. 58 | type OUIdentifier struct { 59 | // Certificate represents the second certificate in a certification chain. 60 | // (Notice that the first certificate in a certification chain is supposed 61 | // to be the certificate of an identity). 62 | // It must correspond to the certificate of root or intermediate CA 63 | // recognized by the MSP this message belongs to. 64 | // Starting from this certificate, a certification chain is computed 65 | // and bound to the OrganizationUnitIdentifier specified. 66 | Certificate *x509.Certificate 67 | // OrganizationUnitIdentifier defines the organizational unit under the 68 | // MSP identified with MSPIdentifier. 69 | OrganizationalUnitIdentifier string 70 | } 71 | 72 | // NodeOUs contains configuration to tell apart clients from peers from orderers 73 | // based on OUs. If NodeOUs recognition is enabled then an msp identity 74 | // that does not contain any of the specified OU will be considered invalid. 75 | type NodeOUs struct { 76 | // If true then an msp identity that does not contain any of the specified OU will be considered invalid. 77 | Enable bool 78 | // OU Identifier of the clients. 79 | ClientOUIdentifier OUIdentifier 80 | // OU Identifier of the peers. 81 | PeerOUIdentifier OUIdentifier 82 | // OU Identifier of the admins. 83 | AdminOUIdentifier OUIdentifier 84 | // OU Identifier of the orderers. 85 | OrdererOUIdentifier OUIdentifier 86 | } 87 | -------------------------------------------------------------------------------- /configtx/orderer/orderer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package orderer 8 | 9 | import ( 10 | "crypto/x509" 11 | ) 12 | 13 | const ( 14 | 15 | // ConsensusStateNormal indicates normal orderer operation. 16 | ConsensusStateNormal ConsensusState = "STATE_NORMAL" 17 | 18 | // ConsensusStateMaintenance indicates the orderer is in consensus type migration. 19 | ConsensusStateMaintenance ConsensusState = "STATE_MAINTENANCE" 20 | 21 | // ConsensusTypeSolo identifies the solo consensus implementation. 22 | // Deprecated: the solo consensus type is no longer supported 23 | ConsensusTypeSolo = "solo" 24 | 25 | // ConsensusTypeKafka identifies the Kafka-based consensus implementation. 26 | // Deprecated: the kafka consensus type is no longer supported 27 | ConsensusTypeKafka = "kafka" 28 | 29 | // ConsensusTypeEtcdRaft identifies the Raft-based consensus implementation. 30 | ConsensusTypeEtcdRaft = "etcdraft" 31 | 32 | // KafkaBrokersKey is the common.ConfigValue type key name for the KafkaBrokers message. 33 | // Deprecated: the kafka consensus type is no longer supported 34 | KafkaBrokersKey = "KafkaBrokers" 35 | 36 | // ConsensusTypeKey is the common.ConfigValue type key name for the ConsensusType message. 37 | ConsensusTypeKey = "ConsensusType" 38 | 39 | // BatchSizeKey is the common.ConfigValue type key name for the BatchSize message. 40 | BatchSizeKey = "BatchSize" 41 | 42 | // BatchTimeoutKey is the common.ConfigValue type key name for the BatchTimeout message. 43 | BatchTimeoutKey = "BatchTimeout" 44 | 45 | // ChannelRestrictionsKey is the key name for the ChannelRestrictions message. 46 | ChannelRestrictionsKey = "ChannelRestrictions" 47 | ) 48 | 49 | // ConsensusState defines the orderer mode of operation. 50 | // Options: `ConsensusStateNormal` and `ConsensusStateMaintenance` 51 | type ConsensusState string 52 | 53 | // BatchSize is the configuration affecting the size of batches. 54 | type BatchSize struct { 55 | // MaxMessageCount is the max message count. 56 | MaxMessageCount uint32 57 | // AbsoluteMaxBytes is the max block size (not including headers). 58 | AbsoluteMaxBytes uint32 59 | // PreferredMaxBytes is the preferred size of blocks. 60 | PreferredMaxBytes uint32 61 | } 62 | 63 | // Kafka is a list of Kafka broker endpoints. 64 | // Deprecated: the kafka consensus type is no longer supported 65 | type Kafka struct { 66 | // Brokers contains the addresses of *at least two* kafka brokers 67 | // Must be in `IP:port` notation 68 | Brokers []string 69 | } 70 | 71 | // EtcdRaft is serialized and set as the value of ConsensusType.Metadata in 72 | // a channel configuration when the ConsensusType.Type is set to "etcdraft". 73 | type EtcdRaft struct { 74 | Consenters []Consenter 75 | Options EtcdRaftOptions 76 | } 77 | 78 | // EtcdRaftOptions to be specified for all the etcd/raft nodes. 79 | // These can be modified on a per-channel basis. 80 | type EtcdRaftOptions struct { 81 | TickInterval string 82 | ElectionTick uint32 83 | HeartbeatTick uint32 84 | MaxInflightBlocks uint32 85 | // Take snapshot when cumulative data exceeds certain size in bytes. 86 | SnapshotIntervalSize uint32 87 | } 88 | 89 | // Consenter represents a consenting node (i.e. replica). 90 | type Consenter struct { 91 | Address EtcdAddress 92 | ClientTLSCert *x509.Certificate 93 | ServerTLSCert *x509.Certificate 94 | } 95 | 96 | // EtcdAddress contains the hostname and port for an endpoint. 97 | type EtcdAddress struct { 98 | Host string 99 | Port int 100 | } 101 | -------------------------------------------------------------------------------- /configtx/organization.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "fmt" 11 | 12 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" 14 | pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" 15 | "google.golang.org/protobuf/proto" 16 | ) 17 | 18 | // newOrgConfigGroup returns an config group for an organization. 19 | // It defines the crypto material for the organization (its MSP). 20 | // It sets the mod_policy of all elements to "Admins". 21 | func newOrgConfigGroup(org Organization) (*cb.ConfigGroup, error) { 22 | orgGroup := newConfigGroup() 23 | orgGroup.ModPolicy = AdminsPolicyKey 24 | 25 | if org.ModPolicy != "" { 26 | orgGroup.ModPolicy = org.ModPolicy 27 | } 28 | 29 | if err := setPolicies(orgGroup, org.Policies); err != nil { 30 | return nil, err 31 | } 32 | 33 | fabricMSPConfig, err := org.MSP.toProto() 34 | if err != nil { 35 | return nil, fmt.Errorf("converting fabric msp config to proto: %v", err) 36 | } 37 | 38 | conf, err := proto.Marshal(fabricMSPConfig) 39 | if err != nil { 40 | return nil, fmt.Errorf("marshaling msp config: %v", err) 41 | } 42 | 43 | // mspConfig defaults type to FABRIC which implements an X.509 based provider 44 | mspConfig := &mb.MSPConfig{ 45 | Config: conf, 46 | } 47 | 48 | err = setValue(orgGroup, mspValue(mspConfig), AdminsPolicyKey) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return orgGroup, nil 54 | } 55 | 56 | func newOrdererOrgConfigGroup(org Organization) (*cb.ConfigGroup, error) { 57 | orgGroup, err := newOrgConfigGroup(org) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | // OrdererEndpoints are orderer org specific and are only added when specified for orderer orgs 63 | if len(org.OrdererEndpoints) > 0 { 64 | err := setValue(orgGroup, endpointsValue(org.OrdererEndpoints), AdminsPolicyKey) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | return orgGroup, nil 71 | } 72 | 73 | func newApplicationOrgConfigGroup(org Organization) (*cb.ConfigGroup, error) { 74 | orgGroup, err := newOrgConfigGroup(org) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | // AnchorPeers are application org specific and are only added when specified for application orgs 80 | anchorProtos := make([]*pb.AnchorPeer, len(org.AnchorPeers)) 81 | for i, anchorPeer := range org.AnchorPeers { 82 | anchorProtos[i] = &pb.AnchorPeer{ 83 | Host: anchorPeer.Host, 84 | Port: int32(anchorPeer.Port), 85 | } 86 | } 87 | 88 | // Avoid adding an unnecessary anchor peers element when one is not required 89 | // This helps prevent a delta from the orderer system channel when computing 90 | // more complex channel creation transactions 91 | if len(anchorProtos) > 0 { 92 | err := setValue(orgGroup, anchorPeersValue(anchorProtos), AdminsPolicyKey) 93 | if err != nil { 94 | return nil, fmt.Errorf("failed to add anchor peers value: %v", err) 95 | } 96 | } 97 | 98 | return orgGroup, nil 99 | } 100 | 101 | // getOrganization returns a basic Organization struct from org config group. 102 | func getOrganization(orgGroup *cb.ConfigGroup, orgName string) (Organization, error) { 103 | policies, err := getPolicies(orgGroup.Policies) 104 | if err != nil { 105 | return Organization{}, err 106 | } 107 | 108 | msp, err := getMSPConfig(orgGroup) 109 | if err != nil { 110 | return Organization{}, err 111 | } 112 | 113 | var anchorPeers []Address 114 | _, ok := orgGroup.Values[AnchorPeersKey] 115 | if ok { 116 | anchorProtos := &pb.AnchorPeers{} 117 | err = unmarshalConfigValueAtKey(orgGroup, AnchorPeersKey, anchorProtos) 118 | if err != nil { 119 | return Organization{}, err 120 | } 121 | 122 | for _, anchorProto := range anchorProtos.AnchorPeers { 123 | anchorPeers = append(anchorPeers, Address{ 124 | Host: anchorProto.Host, 125 | Port: int(anchorProto.Port), 126 | }) 127 | } 128 | } 129 | 130 | return Organization{ 131 | Name: orgName, 132 | Policies: policies, 133 | MSP: msp, 134 | AnchorPeers: anchorPeers, 135 | }, nil 136 | } 137 | -------------------------------------------------------------------------------- /configtx/organization_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "testing" 13 | 14 | "github.com/hyperledger/fabric-config/protolator" 15 | "github.com/hyperledger/fabric-config/protolator/protoext/ordererext" 16 | . "github.com/onsi/gomega" 17 | ) 18 | 19 | func TestOrganization(t *testing.T) { 20 | t.Parallel() 21 | gt := NewGomegaWithT(t) 22 | 23 | expectedOrg := baseApplicationOrg(t) 24 | expectedOrg.AnchorPeers = nil 25 | orgGroup, err := newOrgConfigGroup(expectedOrg) 26 | gt.Expect(err).NotTo(HaveOccurred()) 27 | 28 | org, err := getOrganization(orgGroup, "Org1") 29 | gt.Expect(err).NotTo(HaveOccurred()) 30 | gt.Expect(expectedOrg).To(Equal(org)) 31 | } 32 | 33 | func TestNewOrgConfigGroup(t *testing.T) { 34 | t.Parallel() 35 | 36 | t.Run("success", func(t *testing.T) { 37 | t.Parallel() 38 | gt := NewGomegaWithT(t) 39 | 40 | baseSystemChannelProfile, _, _ := baseSystemChannelProfile(t) 41 | org := baseSystemChannelProfile.Orderer.Organizations[0] 42 | configGroup, err := newOrdererOrgConfigGroup(org) 43 | gt.Expect(err).NotTo(HaveOccurred()) 44 | 45 | certBase64, crlBase64 := certCRLBase64(t, org.MSP) 46 | 47 | // The organization is from network.BasicSolo Profile 48 | // configtxgen -printOrg Org1 49 | expectedPrintOrg := fmt.Sprintf(` 50 | { 51 | "groups": {}, 52 | "mod_policy": "Admins", 53 | "policies": { 54 | "Admins": { 55 | "mod_policy": "Admins", 56 | "policy": { 57 | "type": 3, 58 | "value": { 59 | "rule": "MAJORITY", 60 | "sub_policy": "Admins" 61 | } 62 | }, 63 | "version": "0" 64 | }, 65 | "Endorsement": { 66 | "mod_policy": "Admins", 67 | "policy": { 68 | "type": 3, 69 | "value": { 70 | "rule": "MAJORITY", 71 | "sub_policy": "Endorsement" 72 | } 73 | }, 74 | "version": "0" 75 | }, 76 | "Readers": { 77 | "mod_policy": "Admins", 78 | "policy": { 79 | "type": 3, 80 | "value": { 81 | "rule": "ANY", 82 | "sub_policy": "Readers" 83 | } 84 | }, 85 | "version": "0" 86 | }, 87 | "Writers": { 88 | "mod_policy": "Admins", 89 | "policy": { 90 | "type": 3, 91 | "value": { 92 | "rule": "ANY", 93 | "sub_policy": "Writers" 94 | } 95 | }, 96 | "version": "0" 97 | } 98 | }, 99 | "values": { 100 | "Endpoints": { 101 | "mod_policy": "Admins", 102 | "value": { 103 | "addresses": [ 104 | "localhost:123" 105 | ] 106 | }, 107 | "version": "0" 108 | }, 109 | "MSP": { 110 | "mod_policy": "Admins", 111 | "value": { 112 | "config": { 113 | "admins": [ 114 | "%[1]s" 115 | ], 116 | "crypto_config": { 117 | "identity_identifier_hash_function": "SHA256", 118 | "signature_hash_family": "SHA3" 119 | }, 120 | "fabric_node_ous": { 121 | "admin_ou_identifier": { 122 | "certificate": "%[1]s", 123 | "organizational_unit_identifier": "OUID" 124 | }, 125 | "client_ou_identifier": { 126 | "certificate": "%[1]s", 127 | "organizational_unit_identifier": "OUID" 128 | }, 129 | "enable": false, 130 | "orderer_ou_identifier": { 131 | "certificate": "%[1]s", 132 | "organizational_unit_identifier": "OUID" 133 | }, 134 | "peer_ou_identifier": { 135 | "certificate": "%[1]s", 136 | "organizational_unit_identifier": "OUID" 137 | } 138 | }, 139 | "intermediate_certs": [ 140 | "%[1]s" 141 | ], 142 | "name": "MSPID", 143 | "organizational_unit_identifiers": [ 144 | { 145 | "certificate": "%[1]s", 146 | "organizational_unit_identifier": "OUID" 147 | } 148 | ], 149 | "revocation_list": [ 150 | "%[2]s" 151 | ], 152 | "root_certs": [ 153 | "%[1]s" 154 | ], 155 | "signing_identity": null, 156 | "tls_intermediate_certs": [ 157 | "%[1]s" 158 | ], 159 | "tls_root_certs": [ 160 | "%[1]s" 161 | ] 162 | }, 163 | "type": 0 164 | }, 165 | "version": "0" 166 | } 167 | }, 168 | "version": "0" 169 | } 170 | `, certBase64, crlBase64) 171 | 172 | buf := bytes.Buffer{} 173 | err = protolator.DeepMarshalJSON(&buf, &ordererext.DynamicOrdererOrgGroup{ConfigGroup: configGroup}) 174 | gt.Expect(err).NotTo(HaveOccurred()) 175 | 176 | gt.Expect(buf.String()).To(MatchJSON(expectedPrintOrg)) 177 | }) 178 | } 179 | 180 | func TestNewOrgConfigGroupFailure(t *testing.T) { 181 | t.Parallel() 182 | 183 | gt := NewGomegaWithT(t) 184 | 185 | baseSystemChannelProfile, _, _ := baseSystemChannelProfile(t) 186 | baseOrg := baseSystemChannelProfile.Orderer.Organizations[0] 187 | baseOrg.Policies = nil 188 | 189 | configGroup, err := newOrgConfigGroup(baseOrg) 190 | gt.Expect(configGroup).To(BeNil()) 191 | gt.Expect(err).To(MatchError("no policies defined")) 192 | } 193 | 194 | func baseApplicationOrg(t *testing.T) Organization { 195 | msp, _ := baseMSP(t) 196 | return Organization{ 197 | Name: "Org1", 198 | Policies: standardPolicies(), 199 | MSP: msp, 200 | AnchorPeers: []Address{ 201 | {Host: "host3", Port: 123}, 202 | }, 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /configtx/policies.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/hyperledger/fabric-config/configtx/internal/policydsl" 16 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 17 | mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" 18 | "google.golang.org/protobuf/proto" 19 | ) 20 | 21 | // getPolicies returns a map of Policy from given map of ConfigPolicy in organization config group. 22 | func getPolicies(policies map[string]*cb.ConfigPolicy) (map[string]Policy, error) { 23 | p := map[string]Policy{} 24 | 25 | for name, policy := range policies { 26 | switch cb.Policy_PolicyType(policy.Policy.Type) { 27 | case cb.Policy_IMPLICIT_META: 28 | imp := &cb.ImplicitMetaPolicy{} 29 | err := proto.Unmarshal(policy.Policy.Value, imp) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | rule, err := implicitMetaToString(imp) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | p[name] = Policy{ 40 | Type: ImplicitMetaPolicyType, 41 | Rule: rule, 42 | ModPolicy: policy.GetModPolicy(), 43 | } 44 | case cb.Policy_SIGNATURE: 45 | sp := &cb.SignaturePolicyEnvelope{} 46 | err := proto.Unmarshal(policy.Policy.Value, sp) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | rule, err := signatureMetaToString(sp) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | p[name] = Policy{ 57 | Type: SignaturePolicyType, 58 | Rule: rule, 59 | ModPolicy: policy.GetModPolicy(), 60 | } 61 | default: 62 | return nil, fmt.Errorf("unknown policy type: %v", policy.Policy.Type) 63 | } 64 | } 65 | 66 | return p, nil 67 | } 68 | 69 | // implicitMetaToString converts a *cb.ImplicitMetaPolicy to a string representation. 70 | func implicitMetaToString(imp *cb.ImplicitMetaPolicy) (string, error) { 71 | var args string 72 | 73 | switch imp.Rule { 74 | case cb.ImplicitMetaPolicy_ANY: 75 | args += cb.ImplicitMetaPolicy_ANY.String() 76 | case cb.ImplicitMetaPolicy_ALL: 77 | args += cb.ImplicitMetaPolicy_ALL.String() 78 | case cb.ImplicitMetaPolicy_MAJORITY: 79 | args += cb.ImplicitMetaPolicy_MAJORITY.String() 80 | default: 81 | return "", fmt.Errorf("unknown implicit meta policy rule type %v", imp.Rule) 82 | } 83 | 84 | args = args + " " + imp.SubPolicy 85 | 86 | return args, nil 87 | } 88 | 89 | // signatureMetaToString converts a *cb.SignaturePolicyEnvelope to a string representation. 90 | func signatureMetaToString(sig *cb.SignaturePolicyEnvelope) (string, error) { 91 | var roles []string 92 | 93 | for _, id := range sig.Identities { 94 | role, err := mspPrincipalToString(id) 95 | if err != nil { 96 | return "", err 97 | } 98 | 99 | roles = append(roles, role) 100 | } 101 | 102 | return signaturePolicyToString(sig.Rule, roles) 103 | } 104 | 105 | // mspPrincipalToString converts a *mb.MSPPrincipal to a string representation. 106 | func mspPrincipalToString(principal *mb.MSPPrincipal) (string, error) { 107 | switch principal.PrincipalClassification { 108 | case mb.MSPPrincipal_ROLE: 109 | var res strings.Builder 110 | 111 | role := &mb.MSPRole{} 112 | 113 | err := proto.Unmarshal(principal.Principal, role) 114 | if err != nil { 115 | return "", err 116 | } 117 | 118 | res.WriteString("'") 119 | res.WriteString(role.MspIdentifier) 120 | res.WriteString(".") 121 | res.WriteString(strings.ToLower(role.Role.String())) 122 | res.WriteString("'") 123 | 124 | return res.String(), nil 125 | // TODO: currently fabric only support string to principle convertion for 126 | // type ROLE. Implement MSPPrinciple to String for types ORGANIZATION_UNIT, 127 | // IDENTITY, ANONYMITY, and GOMBINED once we have support from fabric. 128 | case mb.MSPPrincipal_ORGANIZATION_UNIT: 129 | return "", nil 130 | case mb.MSPPrincipal_IDENTITY: 131 | return "", nil 132 | case mb.MSPPrincipal_ANONYMITY: 133 | return "", nil 134 | case mb.MSPPrincipal_COMBINED: 135 | return "", nil 136 | default: 137 | return "", fmt.Errorf("unknown MSP principal classiciation %v", principal.PrincipalClassification) 138 | } 139 | } 140 | 141 | // signaturePolicyToString recursively converts a *cb.SignaturePolicy to a 142 | // string representation. 143 | func signaturePolicyToString(sig *cb.SignaturePolicy, IDs []string) (string, error) { 144 | switch sig.Type.(type) { 145 | case *cb.SignaturePolicy_NOutOf_: 146 | nOutOf := sig.GetNOutOf() 147 | 148 | var policies []string 149 | 150 | var res strings.Builder 151 | 152 | // get gate values 153 | gate := policydsl.GateOutOf 154 | if nOutOf.N == 1 { 155 | gate = policydsl.GateOr 156 | } 157 | 158 | if nOutOf.N == int32(len(nOutOf.Rules)) { 159 | gate = policydsl.GateAnd 160 | } 161 | 162 | if gate == policydsl.GateOutOf { 163 | policies = append(policies, strconv.Itoa(int(nOutOf.N))) 164 | } 165 | 166 | // get subpolicies recursively 167 | for _, rule := range nOutOf.Rules { 168 | subPolicy, err := signaturePolicyToString(rule, IDs) 169 | if err != nil { 170 | return "", err 171 | } 172 | 173 | policies = append(policies, subPolicy) 174 | } 175 | 176 | res.WriteString(strings.ToUpper(gate)) 177 | res.WriteString("(") 178 | res.WriteString(strings.Join(policies, ", ")) 179 | res.WriteString(")") 180 | 181 | return res.String(), nil 182 | case *cb.SignaturePolicy_SignedBy: 183 | return IDs[sig.GetSignedBy()], nil 184 | default: 185 | return "", fmt.Errorf("unknown signature policy type %v", sig.Type) 186 | } 187 | } 188 | 189 | func setPolicies(cg *cb.ConfigGroup, policyMap map[string]Policy) error { 190 | if policyMap == nil { 191 | return errors.New("no policies defined") 192 | } 193 | 194 | if _, ok := policyMap[AdminsPolicyKey]; !ok { 195 | return errors.New("no Admins policy defined") 196 | } 197 | 198 | if _, ok := policyMap[ReadersPolicyKey]; !ok { 199 | return errors.New("no Readers policy defined") 200 | } 201 | 202 | if _, ok := policyMap[WritersPolicyKey]; !ok { 203 | return errors.New("no Writers policy defined") 204 | } 205 | 206 | cg.Policies = make(map[string]*cb.ConfigPolicy) 207 | for policyName, policy := range policyMap { 208 | err := setPolicy(cg, policyName, policy) 209 | if err != nil { 210 | return err 211 | } 212 | } 213 | 214 | return nil 215 | } 216 | 217 | func setPolicy(cg *cb.ConfigGroup, policyName string, policy Policy) error { 218 | if cg.Policies == nil { 219 | cg.Policies = make(map[string]*cb.ConfigPolicy) 220 | } 221 | 222 | switch policy.Type { 223 | case ImplicitMetaPolicyType: 224 | imp, err := implicitMetaFromString(policy.Rule) 225 | if err != nil { 226 | return fmt.Errorf("invalid implicit meta policy rule: '%s': %v", policy.Rule, err) 227 | } 228 | 229 | implicitMetaPolicy, err := proto.Marshal(imp) 230 | if err != nil { 231 | return fmt.Errorf("marshaling implicit meta policy: %v", err) 232 | } 233 | 234 | if policy.ModPolicy == "" { 235 | policy.ModPolicy = AdminsPolicyKey 236 | } 237 | 238 | cg.Policies[policyName] = &cb.ConfigPolicy{ 239 | ModPolicy: policy.ModPolicy, 240 | Policy: &cb.Policy{ 241 | Type: int32(cb.Policy_IMPLICIT_META), 242 | Value: implicitMetaPolicy, 243 | }, 244 | } 245 | case SignaturePolicyType: 246 | sp, err := policydsl.FromString(policy.Rule) 247 | if err != nil { 248 | return fmt.Errorf("invalid signature policy rule: '%s': %v", policy.Rule, err) 249 | } 250 | 251 | signaturePolicy, err := proto.Marshal(sp) 252 | if err != nil { 253 | return fmt.Errorf("marshaling signature policy: %v", err) 254 | } 255 | 256 | if policy.ModPolicy == "" { 257 | policy.ModPolicy = AdminsPolicyKey 258 | } 259 | 260 | cg.Policies[policyName] = &cb.ConfigPolicy{ 261 | ModPolicy: policy.ModPolicy, 262 | Policy: &cb.Policy{ 263 | Type: int32(cb.Policy_SIGNATURE), 264 | Value: signaturePolicy, 265 | }, 266 | } 267 | default: 268 | return fmt.Errorf("unknown policy type: %s", policy.Type) 269 | } 270 | 271 | return nil 272 | } 273 | 274 | // removePolicy removes an existing policy from an group key organization. 275 | func removePolicy(configGroup *cb.ConfigGroup, policyName string, policies map[string]Policy) { 276 | delete(configGroup.Policies, policyName) 277 | } 278 | -------------------------------------------------------------------------------- /configtx/policies_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "testing" 11 | 12 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | . "github.com/onsi/gomega" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | func TestPolicies(t *testing.T) { 18 | t.Parallel() 19 | gt := NewGomegaWithT(t) 20 | 21 | expectedPolicies := map[string]Policy{ 22 | ReadersPolicyKey: { 23 | Type: ImplicitMetaPolicyType, 24 | Rule: "ALL Member", 25 | ModPolicy: AdminsPolicyKey, 26 | }, 27 | WritersPolicyKey: { 28 | Type: ImplicitMetaPolicyType, 29 | Rule: "ANY Member", 30 | ModPolicy: AdminsPolicyKey, 31 | }, 32 | AdminsPolicyKey: { 33 | Type: ImplicitMetaPolicyType, 34 | Rule: "MAJORITY Member", 35 | ModPolicy: AdminsPolicyKey, 36 | }, 37 | "SignaturePolicy": { 38 | Type: SignaturePolicyType, 39 | Rule: "AND('Org1.member', 'Org2.client', OR('Org3.peer', 'Org3.admin'), OUTOF(2, 'Org4.member', 'Org4.peer', 'Org4.admin'))", 40 | ModPolicy: AdminsPolicyKey, 41 | }, 42 | } 43 | orgGroup := newConfigGroup() 44 | err := setPolicies(orgGroup, expectedPolicies) 45 | gt.Expect(err).NotTo(HaveOccurred()) 46 | 47 | policies, err := getPolicies(orgGroup.Policies) 48 | gt.Expect(err).NotTo(HaveOccurred()) 49 | gt.Expect(expectedPolicies).To(Equal(policies)) 50 | 51 | policies, err = getPolicies(nil) 52 | gt.Expect(err).NotTo(HaveOccurred()) 53 | gt.Expect(map[string]Policy{}).To(Equal(policies)) 54 | } 55 | 56 | func TestSetConsortiumChannelCreationPolicy(t *testing.T) { 57 | t.Parallel() 58 | 59 | gt := NewGomegaWithT(t) 60 | 61 | consortiums, _ := baseConsortiums(t) 62 | 63 | consortiumsGroup, err := newConsortiumsGroup(consortiums) 64 | gt.Expect(err).NotTo(HaveOccurred()) 65 | 66 | config := &cb.Config{ 67 | ChannelGroup: &cb.ConfigGroup{ 68 | Groups: map[string]*cb.ConfigGroup{ 69 | ConsortiumsGroupKey: consortiumsGroup, 70 | }, 71 | }, 72 | } 73 | 74 | c := New(config) 75 | 76 | updatedPolicy := Policy{Type: ImplicitMetaPolicyType, Rule: "MAJORITY Admins"} 77 | 78 | consortium1 := c.Consortium("Consortium1") 79 | err = consortium1.SetChannelCreationPolicy(updatedPolicy) 80 | gt.Expect(err).NotTo(HaveOccurred()) 81 | 82 | creationPolicy := consortium1.consortiumGroup.Values[ChannelCreationPolicyKey] 83 | policy := &cb.Policy{} 84 | err = proto.Unmarshal(creationPolicy.Value, policy) 85 | gt.Expect(err).NotTo(HaveOccurred()) 86 | imp := &cb.ImplicitMetaPolicy{} 87 | err = proto.Unmarshal(policy.Value, imp) 88 | gt.Expect(err).NotTo(HaveOccurred()) 89 | gt.Expect(imp.Rule).To(Equal(cb.ImplicitMetaPolicy_MAJORITY)) 90 | gt.Expect(imp.SubPolicy).To(Equal("Admins")) 91 | } 92 | 93 | func TestSetConsortiumChannelCreationPolicyFailures(t *testing.T) { 94 | t.Parallel() 95 | 96 | gt := NewGomegaWithT(t) 97 | 98 | consortiums, _ := baseConsortiums(t) 99 | 100 | consortiumsGroup, err := newConsortiumsGroup(consortiums) 101 | gt.Expect(err).NotTo(HaveOccurred()) 102 | 103 | config := &cb.Config{ 104 | ChannelGroup: &cb.ConfigGroup{ 105 | Groups: map[string]*cb.ConfigGroup{ 106 | ConsortiumsGroupKey: consortiumsGroup, 107 | }, 108 | }, 109 | } 110 | 111 | c := New(config) 112 | 113 | tests := []struct { 114 | name string 115 | consortiumName string 116 | updatedpolicy Policy 117 | expectedErr string 118 | }{ 119 | { 120 | name: "when policy is invalid", 121 | consortiumName: "Consortium1", 122 | updatedpolicy: Policy{Type: ImplicitMetaPolicyType, Rule: "Bad Admins"}, 123 | expectedErr: "invalid implicit meta policy rule 'Bad Admins': unknown rule type 'Bad', expected ALL, ANY, or MAJORITY", 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | tt := tt 129 | t.Run(tt.name, func(t *testing.T) { 130 | gt := NewGomegaWithT(t) 131 | err := c.Consortium(tt.consortiumName).SetChannelCreationPolicy(tt.updatedpolicy) 132 | gt.Expect(err).To(MatchError(tt.expectedErr)) 133 | }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /configtx/signer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "crypto" 11 | "crypto/ecdsa" 12 | "crypto/rand" 13 | "crypto/sha256" 14 | "crypto/x509" 15 | "encoding/asn1" 16 | "encoding/pem" 17 | "fmt" 18 | "io" 19 | "math/big" 20 | 21 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 22 | mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" 23 | "google.golang.org/protobuf/proto" 24 | ) 25 | 26 | // SigningIdentity is an MSP Identity that can be used to sign configuration 27 | // updates. 28 | type SigningIdentity struct { 29 | Certificate *x509.Certificate 30 | PrivateKey crypto.PrivateKey 31 | MSPID string 32 | } 33 | 34 | type ecdsaSignature struct { 35 | R, S *big.Int 36 | } 37 | 38 | // Public returns the public key associated with this signing 39 | // identity's certificate. 40 | func (s *SigningIdentity) Public() crypto.PublicKey { 41 | return s.Certificate.PublicKey 42 | } 43 | 44 | // Sign performs ECDSA sign with this signing identity's private key on the 45 | // given message hashed using SHA-256. It ensures signatures are created with 46 | // Low S values since Fabric normalizes all signatures to Low S. 47 | // See https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#low_s 48 | // for more detail. 49 | func (s *SigningIdentity) Sign(reader io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { 50 | switch pk := s.PrivateKey.(type) { 51 | case *ecdsa.PrivateKey: 52 | hasher := sha256.New() 53 | hasher.Write(msg) 54 | digest := hasher.Sum(nil) 55 | 56 | rr, ss, err := ecdsa.Sign(reader, pk, digest) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // ensure Low S signatures 62 | sig := toLowS( 63 | pk.PublicKey, 64 | ecdsaSignature{ 65 | R: rr, 66 | S: ss, 67 | }, 68 | ) 69 | 70 | return asn1.Marshal(sig) 71 | default: 72 | return nil, fmt.Errorf("signing with private key of type %T not supported", pk) 73 | } 74 | } 75 | 76 | // toLows normalizes all signatures to a canonical form where s is at most 77 | // half the order of the curve. By doing so, it compliant with what Fabric 78 | // expected as well as protect against signature malleability attacks. 79 | func toLowS(key ecdsa.PublicKey, sig ecdsaSignature) ecdsaSignature { 80 | // calculate half order of the curve 81 | halfOrder := new(big.Int).Div(key.Curve.Params().N, big.NewInt(2)) 82 | // check if s is greater than half order of curve 83 | if sig.S.Cmp(halfOrder) == 1 { 84 | // Set s to N - s so that s will be less than or equal to half order 85 | sig.S.Sub(key.Params().N, sig.S) 86 | } 87 | 88 | return sig 89 | } 90 | 91 | // CreateConfigSignature creates a config signature for the the given configuration 92 | // update using the specified signing identity. 93 | func (s *SigningIdentity) CreateConfigSignature(marshaledUpdate []byte) (*cb.ConfigSignature, error) { 94 | signatureHeader, err := s.signatureHeader() 95 | if err != nil { 96 | return nil, fmt.Errorf("creating signature header: %v", err) 97 | } 98 | 99 | header, err := proto.Marshal(signatureHeader) 100 | if err != nil { 101 | return nil, fmt.Errorf("marshaling signature header: %v", err) 102 | } 103 | 104 | configSignature := &cb.ConfigSignature{ 105 | SignatureHeader: header, 106 | } 107 | 108 | configSignature.Signature, err = s.Sign( 109 | rand.Reader, 110 | concatenateBytes(configSignature.SignatureHeader, marshaledUpdate), 111 | nil, 112 | ) 113 | if err != nil { 114 | return nil, fmt.Errorf("signing config update: %v", err) 115 | } 116 | 117 | return configSignature, nil 118 | } 119 | 120 | // SignEnvelope signs an envelope using the SigningIdentity. 121 | func (s *SigningIdentity) SignEnvelope(e *cb.Envelope) error { 122 | signatureHeader, err := s.signatureHeader() 123 | if err != nil { 124 | return fmt.Errorf("creating signature header: %v", err) 125 | } 126 | 127 | sHeader, err := proto.Marshal(signatureHeader) 128 | if err != nil { 129 | return fmt.Errorf("marshaling signature header: %v", err) 130 | } 131 | 132 | payload := &cb.Payload{} 133 | err = proto.Unmarshal(e.Payload, payload) 134 | if err != nil { 135 | return fmt.Errorf("unmarshaling envelope payload: %v", err) 136 | } 137 | payload.Header.SignatureHeader = sHeader 138 | 139 | payloadBytes, err := proto.Marshal(payload) 140 | if err != nil { 141 | return fmt.Errorf("marshaling payload: %v", err) 142 | } 143 | 144 | sig, err := s.Sign(rand.Reader, payloadBytes, nil) 145 | if err != nil { 146 | return fmt.Errorf("signing envelope payload: %v", err) 147 | } 148 | 149 | e.Payload = payloadBytes 150 | e.Signature = sig 151 | 152 | return nil 153 | } 154 | 155 | func (s *SigningIdentity) signatureHeader() (*cb.SignatureHeader, error) { 156 | pemBytes := pem.EncodeToMemory(&pem.Block{ 157 | Type: "CERTIFICATE", 158 | Bytes: s.Certificate.Raw, 159 | }) 160 | 161 | idBytes, err := proto.Marshal(&mb.SerializedIdentity{ 162 | Mspid: s.MSPID, 163 | IdBytes: pemBytes, 164 | }) 165 | if err != nil { 166 | return nil, fmt.Errorf("marshaling serialized identity: %v", err) 167 | } 168 | 169 | nonce, err := newNonce() 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | return &cb.SignatureHeader{ 175 | Creator: idBytes, 176 | Nonce: nonce, 177 | }, nil 178 | } 179 | 180 | // newNonce generates a 24-byte nonce using the crypto/rand package. 181 | func newNonce() ([]byte, error) { 182 | nonce := make([]byte, 24) 183 | 184 | _, err := rand.Read(nonce) 185 | if err != nil { 186 | return nil, fmt.Errorf("failed to get random bytes: %v", err) 187 | } 188 | 189 | return nonce, nil 190 | } 191 | -------------------------------------------------------------------------------- /configtx/update.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package configtx 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | 13 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | // Compute computes the difference between two *cb.Configs and returns the 18 | // ReadSet and WriteSet diff as a *cb.ConfigUpdate 19 | func computeConfigUpdate(original, updated *cb.Config) (*cb.ConfigUpdate, error) { 20 | if original.ChannelGroup == nil { 21 | return nil, fmt.Errorf("no channel group included for original config") 22 | } 23 | 24 | if updated.ChannelGroup == nil { 25 | return nil, fmt.Errorf("no channel group included for updated config") 26 | } 27 | 28 | readSet, writeSet, groupUpdated := computeGroupUpdate(original.ChannelGroup, updated.ChannelGroup) 29 | if !groupUpdated { 30 | return nil, fmt.Errorf("no differences detected between original and updated config") 31 | } 32 | 33 | updated.Sequence = original.Sequence + 1 34 | 35 | return &cb.ConfigUpdate{ 36 | ReadSet: readSet, 37 | WriteSet: writeSet, 38 | }, nil 39 | } 40 | 41 | func computePoliciesMapUpdate(original, updated map[string]*cb.ConfigPolicy) (readSet, writeSet, sameSet map[string]*cb.ConfigPolicy, updatedMembers bool) { 42 | readSet = make(map[string]*cb.ConfigPolicy) 43 | writeSet = make(map[string]*cb.ConfigPolicy) 44 | 45 | // All modified config goes into the read/write sets, but in case the map membership changes, we retain the 46 | // config which was the same to add to the read/write sets 47 | sameSet = make(map[string]*cb.ConfigPolicy) 48 | 49 | for policyName, originalPolicy := range original { 50 | updatedPolicy, ok := updated[policyName] 51 | if !ok { 52 | updatedMembers = true 53 | continue 54 | } 55 | 56 | if originalPolicy.ModPolicy == updatedPolicy.ModPolicy && proto.Equal(originalPolicy.Policy, updatedPolicy.Policy) { 57 | sameSet[policyName] = &cb.ConfigPolicy{ 58 | Version: originalPolicy.Version, 59 | } 60 | continue 61 | } 62 | 63 | updatedPolicy.Version = originalPolicy.Version + 1 64 | writeSet[policyName] = &cb.ConfigPolicy{ 65 | Version: originalPolicy.Version + 1, 66 | ModPolicy: updatedPolicy.ModPolicy, 67 | Policy: updatedPolicy.Policy, 68 | } 69 | } 70 | 71 | for policyName, updatedPolicy := range updated { 72 | if _, ok := original[policyName]; ok { 73 | // If the updatedPolicy is in the original set of policies, it was already handled 74 | continue 75 | } 76 | updatedMembers = true 77 | writeSet[policyName] = &cb.ConfigPolicy{ 78 | Version: 0, 79 | ModPolicy: updatedPolicy.ModPolicy, 80 | Policy: updatedPolicy.Policy, 81 | } 82 | } 83 | 84 | return 85 | } 86 | 87 | func computeValuesMapUpdate(original, updated map[string]*cb.ConfigValue) (readSet, writeSet, sameSet map[string]*cb.ConfigValue, updatedMembers bool) { 88 | readSet = make(map[string]*cb.ConfigValue) 89 | writeSet = make(map[string]*cb.ConfigValue) 90 | 91 | // All modified config goes into the read/write sets, but in case the map membership changes, we retain the 92 | // config which was the same to add to the read/write sets 93 | sameSet = make(map[string]*cb.ConfigValue) 94 | 95 | for valueName, originalValue := range original { 96 | updatedValue, ok := updated[valueName] 97 | if !ok { 98 | updatedMembers = true 99 | continue 100 | } 101 | 102 | if originalValue.ModPolicy == updatedValue.ModPolicy && bytes.Equal(originalValue.Value, updatedValue.Value) { 103 | sameSet[valueName] = &cb.ConfigValue{ 104 | Version: originalValue.Version, 105 | } 106 | continue 107 | } 108 | 109 | updatedValue.Version = originalValue.Version + 1 110 | writeSet[valueName] = &cb.ConfigValue{ 111 | Version: originalValue.Version + 1, 112 | ModPolicy: updatedValue.ModPolicy, 113 | Value: updatedValue.Value, 114 | } 115 | } 116 | 117 | for valueName, updatedValue := range updated { 118 | if _, ok := original[valueName]; ok { 119 | // If the updatedValue is in the original set of values, it was already handled 120 | continue 121 | } 122 | updatedMembers = true 123 | writeSet[valueName] = &cb.ConfigValue{ 124 | Version: 0, 125 | ModPolicy: updatedValue.ModPolicy, 126 | Value: updatedValue.Value, 127 | } 128 | } 129 | 130 | return 131 | } 132 | 133 | func computeGroupsMapUpdate(original, updated map[string]*cb.ConfigGroup) (readSet, writeSet, sameSet map[string]*cb.ConfigGroup, updatedMembers bool) { 134 | readSet = make(map[string]*cb.ConfigGroup) 135 | writeSet = make(map[string]*cb.ConfigGroup) 136 | 137 | // All modified config goes into the read/write sets, but in case the map membership changes, we retain the 138 | // config which was the same to add to the read/write sets 139 | sameSet = make(map[string]*cb.ConfigGroup) 140 | 141 | for groupName, originalGroup := range original { 142 | updatedGroup, ok := updated[groupName] 143 | if !ok { 144 | updatedMembers = true 145 | continue 146 | } 147 | 148 | groupReadSet, groupWriteSet, groupUpdated := computeGroupUpdate(originalGroup, updatedGroup) 149 | if !groupUpdated { 150 | sameSet[groupName] = groupReadSet 151 | continue 152 | } 153 | 154 | readSet[groupName] = groupReadSet 155 | writeSet[groupName] = groupWriteSet 156 | 157 | } 158 | 159 | for groupName, updatedGroup := range updated { 160 | if _, ok := original[groupName]; ok { 161 | // If the updatedGroup is in the original set of groups, it was already handled 162 | continue 163 | } 164 | updatedMembers = true 165 | _, groupWriteSet, _ := computeGroupUpdate(newConfigGroup(), updatedGroup) 166 | writeSet[groupName] = &cb.ConfigGroup{ 167 | Version: 0, 168 | ModPolicy: updatedGroup.ModPolicy, 169 | Policies: groupWriteSet.Policies, 170 | Values: groupWriteSet.Values, 171 | Groups: groupWriteSet.Groups, 172 | } 173 | } 174 | 175 | return 176 | } 177 | 178 | func computeGroupUpdate(original, updated *cb.ConfigGroup) (readSet, writeSet *cb.ConfigGroup, updatedGroup bool) { 179 | readSetPolicies, writeSetPolicies, sameSetPolicies, policiesMembersUpdated := computePoliciesMapUpdate(original.Policies, updated.Policies) 180 | readSetValues, writeSetValues, sameSetValues, valuesMembersUpdated := computeValuesMapUpdate(original.Values, updated.Values) 181 | readSetGroups, writeSetGroups, sameSetGroups, groupsMembersUpdated := computeGroupsMapUpdate(original.Groups, updated.Groups) 182 | 183 | // If the updated group is 'Equal' to the updated group (none of the members nor the mod policy changed) 184 | if !(policiesMembersUpdated || valuesMembersUpdated || groupsMembersUpdated || original.ModPolicy != updated.ModPolicy) { 185 | 186 | // If there were no modified entries in any of the policies/values/groups maps 187 | if len(readSetPolicies) == 0 && 188 | len(writeSetPolicies) == 0 && 189 | len(readSetValues) == 0 && 190 | len(writeSetValues) == 0 && 191 | len(readSetGroups) == 0 && 192 | len(writeSetGroups) == 0 { 193 | return &cb.ConfigGroup{ 194 | Version: original.Version, 195 | }, &cb.ConfigGroup{ 196 | Version: original.Version, 197 | }, false 198 | } 199 | 200 | return &cb.ConfigGroup{ 201 | Version: original.Version, 202 | Policies: readSetPolicies, 203 | Values: readSetValues, 204 | Groups: readSetGroups, 205 | }, &cb.ConfigGroup{ 206 | Version: original.Version, 207 | Policies: writeSetPolicies, 208 | Values: writeSetValues, 209 | Groups: writeSetGroups, 210 | }, true 211 | } 212 | 213 | for k, samePolicy := range sameSetPolicies { 214 | readSetPolicies[k] = samePolicy 215 | writeSetPolicies[k] = samePolicy 216 | } 217 | 218 | for k, sameValue := range sameSetValues { 219 | readSetValues[k] = sameValue 220 | writeSetValues[k] = sameValue 221 | } 222 | 223 | for k, sameGroup := range sameSetGroups { 224 | readSetGroups[k] = sameGroup 225 | writeSetGroups[k] = sameGroup 226 | } 227 | 228 | updated.Version = original.Version + 1 229 | 230 | return &cb.ConfigGroup{ 231 | Version: original.Version, 232 | Policies: readSetPolicies, 233 | Values: readSetValues, 234 | Groups: readSetGroups, 235 | }, &cb.ConfigGroup{ 236 | Version: original.Version + 1, 237 | Policies: writeSetPolicies, 238 | Values: writeSetValues, 239 | Groups: writeSetGroups, 240 | ModPolicy: updated.ModPolicy, 241 | }, true 242 | } 243 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger/fabric-config 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Knetic/govaluate v3.0.0+incompatible 7 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 8 | github.com/onsi/gomega v1.9.0 9 | google.golang.org/protobuf v1.33.0 10 | ) 11 | 12 | require ( 13 | github.com/golang/protobuf v1.5.3 // indirect 14 | golang.org/x/net v0.23.0 // indirect 15 | golang.org/x/sys v0.18.0 // indirect 16 | golang.org/x/text v0.14.0 // indirect 17 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect 19 | google.golang.org/grpc v1.57.0 // indirect 20 | gopkg.in/yaml.v2 v2.2.4 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= 2 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 3 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 4 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 7 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 10 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 11 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 12 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk= 13 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= 14 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 15 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 16 | github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= 17 | github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 18 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 19 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 20 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 21 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 22 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 23 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 24 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 27 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 28 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 29 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 30 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 31 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 32 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= 33 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= 34 | google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= 35 | google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= 36 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 37 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 38 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 39 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 42 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 43 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 44 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 45 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 46 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 47 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 48 | -------------------------------------------------------------------------------- /protolator/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protolator 8 | 9 | import ( 10 | "google.golang.org/protobuf/proto" 11 | ) 12 | 13 | // ///////////////////////////////////////////////////////////////////////////////////////////////// 14 | // 15 | // This set of interfaces and methods is designed to allow protos to have Go methods attached 16 | // to them, so that they may be automatically marshaled to human readable JSON (where the 17 | // opaque byte fields are represented as their expanded proto contents) and back once again 18 | // to standard proto messages. 19 | // 20 | // There are currently three different types of interfaces available for protos to implement: 21 | // 22 | // 1. StaticallyOpaque*FieldProto: These interfaces should be implemented by protos which have 23 | // opaque byte fields whose marshaled type is known at compile time. This is mostly true 24 | // for the signature oriented fields like the Envelope.Payload, or Header.ChannelHeader 25 | // 26 | // 2. VariablyOpaque*FieldProto: These interfaces are identical to the StaticallyOpaque*FieldProto 27 | // definitions, with the exception that they are guaranteed to be evaluated after the 28 | // StaticallyOpaque*FieldProto definitions. In particular, this allows for the type selection of 29 | // a VariablyOpaque*FieldProto to depend on data populated by the StaticallyOpaque*FieldProtos. 30 | // For example, the Payload.data field depends upon the Payload.Header.ChannelHeader.type field, 31 | // which is along a statically marshaled path. 32 | // 33 | // 3. Dynamic*FieldProto: These interfaces are for messages which contain other messages whose 34 | // attributes cannot be determined at compile time. For example, a ConfigValue message may evaluate 35 | // the map field values["MSP"] successfully in an organization context, but not at all in a channel 36 | // context. Because go is not a dynamic language, this dynamic behavior must be simulated by 37 | // wrapping the underlying proto message in another type which can be configured at runtime with 38 | // different contextual behavior. (See tests for examples) 39 | // 40 | // ///////////////////////////////////////////////////////////////////////////////////////////////// 41 | 42 | // StaticallyOpaqueFieldProto should be implemented by protos which have bytes fields which 43 | // are the marshaled value of a fixed type 44 | type StaticallyOpaqueFieldProto interface { 45 | // StaticallyOpaqueFields returns the field names which contain opaque data 46 | StaticallyOpaqueFields() []string 47 | 48 | // StaticallyOpaqueFieldProto returns a newly allocated proto message of the correct 49 | // type for the field name. 50 | StaticallyOpaqueFieldProto(name string) (proto.Message, error) 51 | } 52 | 53 | // StaticallyOpaqueMapFieldProto should be implemented by protos which have maps to bytes fields 54 | // which are the marshaled value of a fixed type 55 | type StaticallyOpaqueMapFieldProto interface { 56 | // StaticallyOpaqueMapFields returns the field names which contain opaque data 57 | StaticallyOpaqueMapFields() []string 58 | 59 | // StaticallyOpaqueMapFieldProto returns a newly allocated proto message of the correct 60 | // type for the field name. 61 | StaticallyOpaqueMapFieldProto(name string, key string) (proto.Message, error) 62 | } 63 | 64 | // StaticallyOpaqueSliceFieldProto should be implemented by protos which have maps to bytes fields 65 | // which are the marshaled value of a fixed type 66 | type StaticallyOpaqueSliceFieldProto interface { 67 | // StaticallyOpaqueSliceFields returns the field names which contain opaque data 68 | StaticallyOpaqueSliceFields() []string 69 | 70 | // StaticallyOpaqueSliceFieldProto returns a newly allocated proto message of the correct 71 | // type for the field name. 72 | StaticallyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) 73 | } 74 | 75 | // VariablyOpaqueFieldProto should be implemented by protos which have bytes fields which 76 | // are the marshaled value depends upon the other contents of the proto 77 | type VariablyOpaqueFieldProto interface { 78 | // VariablyOpaqueFields returns the field names which contain opaque data 79 | VariablyOpaqueFields() []string 80 | 81 | // VariablyOpaqueFieldProto returns a newly allocated proto message of the correct 82 | // type for the field name. 83 | VariablyOpaqueFieldProto(name string) (proto.Message, error) 84 | } 85 | 86 | // VariablyOpaqueMapFieldProto should be implemented by protos which have maps to bytes fields 87 | // which are the marshaled value of a a message type determined by the other contents of the proto 88 | type VariablyOpaqueMapFieldProto interface { 89 | // VariablyOpaqueMapFields returns the field names which contain opaque data 90 | VariablyOpaqueMapFields() []string 91 | 92 | // VariablyOpaqueMapFieldProto returns a newly allocated proto message of the correct 93 | // type for the field name. 94 | VariablyOpaqueMapFieldProto(name string, key string) (proto.Message, error) 95 | } 96 | 97 | // VariablyOpaqueSliceFieldProto should be implemented by protos which have maps to bytes fields 98 | // which are the marshaled value of a a message type determined by the other contents of the proto 99 | type VariablyOpaqueSliceFieldProto interface { 100 | // VariablyOpaqueSliceFields returns the field names which contain opaque data 101 | VariablyOpaqueSliceFields() []string 102 | 103 | // VariablyOpaqueSliceFieldProto returns a newly allocated proto message of the correct 104 | // type for the field name. 105 | VariablyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) 106 | } 107 | 108 | // DynamicFieldProto should be implemented by protos which have nested fields whose attributes 109 | // (such as their opaque types) cannot be determined until runtime 110 | type DynamicFieldProto interface { 111 | // DynamicFields returns the field names which are dynamic 112 | DynamicFields() []string 113 | 114 | // DynamicFieldProto returns a newly allocated dynamic message, decorating an underlying 115 | // proto message with the runtime determined function 116 | DynamicFieldProto(name string, underlying proto.Message) (proto.Message, error) 117 | } 118 | 119 | // DynamicMapFieldProto should be implemented by protos which have maps to messages whose attributes 120 | // (such as their opaque types) cannot be determined until runtime 121 | type DynamicMapFieldProto interface { 122 | // DynamicMapFields returns the field names which are dynamic 123 | DynamicMapFields() []string 124 | 125 | // DynamicMapFieldProto returns a newly allocated dynamic message, decorating an underlying 126 | // proto message with the runtime determined function 127 | DynamicMapFieldProto(name string, key string, underlying proto.Message) (proto.Message, error) 128 | } 129 | 130 | // DynamicSliceFieldProto should be implemented by protos which have slices of messages whose attributes 131 | // (such as their opaque types) cannot be determined until runtime 132 | type DynamicSliceFieldProto interface { 133 | // DynamicSliceFields returns the field names which are dynamic 134 | DynamicSliceFields() []string 135 | 136 | // DynamicSliceFieldProto returns a newly allocated dynamic message, decorating an underlying 137 | // proto message with the runtime determined function 138 | DynamicSliceFieldProto(name string, index int, underlying proto.Message) (proto.Message, error) 139 | } 140 | 141 | // DecoratedProto should be implemented by the dynamic wrappers applied by the Dynamic*FieldProto interfaces 142 | // This is necessary for the proto system to unmarshal, because it discovers proto message type by reflection 143 | // (Rather than by interface definition as it probably should ( https://github.com/golang/protobuf/issues/291 ) 144 | type DecoratedProto interface { 145 | // Underlying returns the underlying proto message which is being dynamically decorated 146 | Underlying() proto.Message 147 | } 148 | -------------------------------------------------------------------------------- /protolator/dynamic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "reflect" 21 | 22 | "google.golang.org/protobuf/proto" 23 | ) 24 | 25 | func dynamicFrom(dynamicMsg func(underlying proto.Message) (proto.Message, error), value interface{}, destType reflect.Type) (reflect.Value, error) { 26 | tree := value.(map[string]interface{}) // Safe, already checked 27 | uMsg := reflect.New(destType.Elem()) 28 | nMsg, err := dynamicMsg(uMsg.Interface().(proto.Message)) // Safe, already checked 29 | if err != nil { 30 | return reflect.Value{}, err 31 | } 32 | if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil { 33 | return reflect.Value{}, err 34 | } 35 | return uMsg, nil 36 | } 37 | 38 | func dynamicTo(dynamicMsg func(underlying proto.Message) (proto.Message, error), value reflect.Value) (interface{}, error) { 39 | nMsg, err := dynamicMsg(value.Interface().(proto.Message)) // Safe, already checked 40 | if err != nil { 41 | return nil, err 42 | } 43 | return recursivelyCreateTreeFromMessage(nMsg) 44 | } 45 | 46 | type dynamicFieldFactory struct{} 47 | 48 | func (dff dynamicFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 49 | dynamicProto, ok := msg.(DynamicFieldProto) 50 | if !ok { 51 | return false 52 | } 53 | 54 | return stringInSlice(fieldName, dynamicProto.DynamicFields()) 55 | } 56 | 57 | func (dff dynamicFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 58 | dynamicProto, _ := msg.(DynamicFieldProto) // Type checked in Handles 59 | 60 | return &plainField{ 61 | baseField: baseField{ 62 | msg: msg, 63 | name: fieldName, 64 | fType: mapStringInterfaceType, 65 | vType: fieldType, 66 | value: fieldValue, 67 | }, 68 | populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) { 69 | return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { 70 | return dynamicProto.DynamicFieldProto(fieldName, underlying) 71 | }, v, dT) 72 | }, 73 | populateTo: func(v reflect.Value) (interface{}, error) { 74 | return dynamicTo(func(underlying proto.Message) (proto.Message, error) { 75 | return dynamicProto.DynamicFieldProto(fieldName, underlying) 76 | }, v) 77 | }, 78 | }, nil 79 | } 80 | 81 | type dynamicMapFieldFactory struct{} 82 | 83 | func (dmff dynamicMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 84 | dynamicProto, ok := msg.(DynamicMapFieldProto) 85 | if !ok { 86 | return false 87 | } 88 | 89 | return stringInSlice(fieldName, dynamicProto.DynamicMapFields()) 90 | } 91 | 92 | func (dmff dynamicMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 93 | dynamicProto := msg.(DynamicMapFieldProto) // Type checked by Handles 94 | 95 | return &mapField{ 96 | baseField: baseField{ 97 | msg: msg, 98 | name: fieldName, 99 | fType: mapStringInterfaceType, 100 | vType: fieldType, 101 | value: fieldValue, 102 | }, 103 | populateFrom: func(k string, v interface{}, dT reflect.Type) (reflect.Value, error) { 104 | return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { 105 | return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying) 106 | }, v, dT) 107 | }, 108 | populateTo: func(k string, v reflect.Value) (interface{}, error) { 109 | return dynamicTo(func(underlying proto.Message) (proto.Message, error) { 110 | return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying) 111 | }, v) 112 | }, 113 | }, nil 114 | } 115 | 116 | type dynamicSliceFieldFactory struct{} 117 | 118 | func (dmff dynamicSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 119 | dynamicProto, ok := msg.(DynamicSliceFieldProto) 120 | if !ok { 121 | return false 122 | } 123 | 124 | return stringInSlice(fieldName, dynamicProto.DynamicSliceFields()) 125 | } 126 | 127 | func (dmff dynamicSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 128 | dynamicProto := msg.(DynamicSliceFieldProto) // Type checked by Handles 129 | 130 | return &sliceField{ 131 | baseField: baseField{ 132 | msg: msg, 133 | name: fieldName, 134 | fType: mapStringInterfaceType, 135 | vType: fieldType, 136 | value: fieldValue, 137 | }, 138 | populateFrom: func(i int, v interface{}, dT reflect.Type) (reflect.Value, error) { 139 | return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { 140 | return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying) 141 | }, v, dT) 142 | }, 143 | populateTo: func(i int, v reflect.Value) (interface{}, error) { 144 | return dynamicTo(func(underlying proto.Message) (proto.Message, error) { 145 | return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying) 146 | }, v) 147 | }, 148 | }, nil 149 | } 150 | -------------------------------------------------------------------------------- /protolator/dynamic_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protolator 8 | 9 | import ( 10 | "bytes" 11 | "testing" 12 | 13 | "github.com/hyperledger/fabric-config/protolator/testprotos" 14 | . "github.com/onsi/gomega" 15 | ) 16 | 17 | func TestPlainDynamicMsg(t *testing.T) { 18 | gt := NewGomegaWithT(t) 19 | 20 | fromPrefix := "from" 21 | toPrefix := "to" 22 | tppff := &testProtoPlainFieldFactory{ 23 | fromPrefix: fromPrefix, 24 | toPrefix: toPrefix, 25 | } 26 | 27 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} 28 | 29 | pfValue := "foo" 30 | startMsg := &testprotos.DynamicMsg{ 31 | DynamicType: "SimpleMsg", 32 | PlainDynamicField: &testprotos.ContextlessMsg{ 33 | OpaqueField: protoMarshalOrPanic(&testprotos.SimpleMsg{ 34 | PlainField: pfValue, 35 | }), 36 | }, 37 | } 38 | 39 | var buffer bytes.Buffer 40 | err := DeepMarshalJSON(&buffer, startMsg) 41 | gt.Expect(err).NotTo(HaveOccurred()) 42 | newMsg := &testprotos.DynamicMsg{} 43 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 44 | gt.Expect(err).NotTo(HaveOccurred()) 45 | gt.Expect(extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField)).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField))) 46 | 47 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicFieldFactory{}} 48 | 49 | buffer.Reset() 50 | err = DeepMarshalJSON(&buffer, startMsg) 51 | gt.Expect(err).NotTo(HaveOccurred()) 52 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 53 | gt.Expect(err).NotTo(HaveOccurred()) 54 | gt.Expect(extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField)).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField))) 55 | } 56 | 57 | func TestMapDynamicMsg(t *testing.T) { 58 | gt := NewGomegaWithT(t) 59 | 60 | fromPrefix := "from" 61 | toPrefix := "to" 62 | tppff := &testProtoPlainFieldFactory{ 63 | fromPrefix: fromPrefix, 64 | toPrefix: toPrefix, 65 | } 66 | 67 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} 68 | 69 | pfValue := "foo" 70 | mapKey := "bar" 71 | startMsg := &testprotos.DynamicMsg{ 72 | DynamicType: "SimpleMsg", 73 | MapDynamicField: map[string]*testprotos.ContextlessMsg{ 74 | mapKey: { 75 | OpaqueField: protoMarshalOrPanic(&testprotos.SimpleMsg{ 76 | PlainField: pfValue, 77 | }), 78 | }, 79 | }, 80 | } 81 | 82 | var buffer bytes.Buffer 83 | err := DeepMarshalJSON(&buffer, startMsg) 84 | gt.Expect(err).NotTo(HaveOccurred()) 85 | newMsg := &testprotos.DynamicMsg{} 86 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 87 | gt.Expect(err).NotTo(HaveOccurred()) 88 | gt.Expect(extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField)).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField))) 89 | 90 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicMapFieldFactory{}} 91 | 92 | buffer.Reset() 93 | err = DeepMarshalJSON(&buffer, startMsg) 94 | gt.Expect(err).NotTo(HaveOccurred()) 95 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 96 | gt.Expect(err).NotTo(HaveOccurred()) 97 | gt.Expect(extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField)).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField))) 98 | } 99 | 100 | func TestSliceDynamicMsg(t *testing.T) { 101 | gt := NewGomegaWithT(t) 102 | 103 | fromPrefix := "from" 104 | toPrefix := "to" 105 | tppff := &testProtoPlainFieldFactory{ 106 | fromPrefix: fromPrefix, 107 | toPrefix: toPrefix, 108 | } 109 | 110 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} 111 | 112 | pfValue := "foo" 113 | startMsg := &testprotos.DynamicMsg{ 114 | DynamicType: "SimpleMsg", 115 | SliceDynamicField: []*testprotos.ContextlessMsg{ 116 | { 117 | OpaqueField: protoMarshalOrPanic(&testprotos.SimpleMsg{ 118 | PlainField: pfValue, 119 | }), 120 | }, 121 | }, 122 | } 123 | 124 | var buffer bytes.Buffer 125 | err := DeepMarshalJSON(&buffer, startMsg) 126 | gt.Expect(err).NotTo(HaveOccurred()) 127 | newMsg := &testprotos.DynamicMsg{} 128 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 129 | gt.Expect(err).NotTo(HaveOccurred()) 130 | gt.Expect(extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField)).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField))) 131 | 132 | fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicSliceFieldFactory{}} 133 | 134 | buffer.Reset() 135 | err = DeepMarshalJSON(&buffer, startMsg) 136 | gt.Expect(err).NotTo(HaveOccurred()) 137 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 138 | gt.Expect(err).NotTo(HaveOccurred()) 139 | gt.Expect(extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField)).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField))) 140 | } 141 | -------------------------------------------------------------------------------- /protolator/integration/integration_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package integration 8 | 9 | import ( 10 | "bytes" 11 | "os" 12 | "testing" 13 | 14 | "github.com/hyperledger/fabric-config/protolator" 15 | cb "github.com/hyperledger/fabric-protos-go-apiv2/common" 16 | mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" 17 | pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" 18 | . "github.com/onsi/gomega" 19 | "google.golang.org/protobuf/proto" 20 | ) 21 | 22 | func bidirectionalMarshal(t *testing.T, doc proto.Message) { 23 | gt := NewGomegaWithT(t) 24 | 25 | var buffer bytes.Buffer 26 | 27 | err := protolator.DeepMarshalJSON(&buffer, doc) 28 | gt.Expect(err).NotTo(HaveOccurred()) 29 | 30 | newRoot := proto.Clone(doc) 31 | proto.Reset(newRoot) 32 | err = protolator.DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newRoot) 33 | gt.Expect(err).NotTo(HaveOccurred()) 34 | 35 | // Note, we cannot do an equality check between newRoot and sampleDoc 36 | // because of the nondeterministic nature of binary proto marshaling 37 | // So instead we re-marshal to JSON which is a deterministic marshaling 38 | // and compare equality there instead 39 | 40 | var remarshaled bytes.Buffer 41 | err = protolator.DeepMarshalJSON(&remarshaled, newRoot) 42 | gt.Expect(err).NotTo(HaveOccurred()) 43 | gt.Expect(remarshaled.String()).To(MatchJSON(buffer.String())) 44 | } 45 | 46 | func TestConfigUpdate(t *testing.T) { 47 | gt := NewGomegaWithT(t) 48 | 49 | blockBin, err := os.ReadFile("testdata/block.pb") 50 | gt.Expect(err).NotTo(HaveOccurred()) 51 | 52 | block := &cb.Block{} 53 | err = proto.Unmarshal(blockBin, block) 54 | gt.Expect(err).NotTo(HaveOccurred()) 55 | 56 | envelope := &cb.Envelope{} 57 | err = proto.Unmarshal(block.Data.Data[0], envelope) 58 | gt.Expect(err).NotTo(HaveOccurred()) 59 | 60 | blockDataPayload := &cb.Payload{} 61 | err = proto.Unmarshal(envelope.Payload, blockDataPayload) 62 | gt.Expect(err).NotTo(HaveOccurred()) 63 | 64 | config := &cb.ConfigEnvelope{} 65 | err = proto.Unmarshal(blockDataPayload.Data, config) 66 | gt.Expect(err).NotTo(HaveOccurred()) 67 | 68 | bidirectionalMarshal(t, &cb.ConfigUpdateEnvelope{ 69 | ConfigUpdate: protoMarshalOrPanic(&cb.ConfigUpdate{ 70 | ReadSet: config.Config.ChannelGroup, 71 | WriteSet: config.Config.ChannelGroup, 72 | }), 73 | }) 74 | } 75 | 76 | func TestIdemix(t *testing.T) { 77 | bidirectionalMarshal(t, &mb.MSPConfig{ 78 | Type: 1, 79 | Config: protoMarshalOrPanic(&mb.IdemixMSPConfig{ 80 | Name: "fooo", 81 | }), 82 | }) 83 | } 84 | 85 | func TestBlock(t *testing.T) { 86 | gt := NewGomegaWithT(t) 87 | 88 | blockBin, err := os.ReadFile("testdata/block.pb") 89 | gt.Expect(err).NotTo(HaveOccurred()) 90 | 91 | block := &cb.Block{} 92 | err = proto.Unmarshal(blockBin, block) 93 | gt.Expect(err).NotTo(HaveOccurred()) 94 | 95 | bidirectionalMarshal(t, block) 96 | } 97 | 98 | func TestEmitDefaultsBug(t *testing.T) { 99 | gt := NewGomegaWithT(t) 100 | 101 | block := &cb.Block{ 102 | Header: &cb.BlockHeader{ 103 | PreviousHash: []byte("foo"), 104 | }, 105 | Data: &cb.BlockData{ 106 | Data: [][]byte{ 107 | protoMarshalOrPanic(&cb.Envelope{ 108 | Payload: protoMarshalOrPanic(&cb.Payload{ 109 | Header: &cb.Header{ 110 | ChannelHeader: protoMarshalOrPanic(&cb.ChannelHeader{ 111 | Type: int32(cb.HeaderType_CONFIG), 112 | }), 113 | }, 114 | }), 115 | Signature: []byte("bar"), 116 | }), 117 | }, 118 | }, 119 | } 120 | 121 | buf := &bytes.Buffer{} 122 | err := protolator.DeepMarshalJSON(buf, block) 123 | gt.Expect(err).NotTo(HaveOccurred()) 124 | gt.Expect(buf.String()).To(MatchJSON(` 125 | { 126 | "data": { 127 | "data": [ 128 | { 129 | "payload": { 130 | "data": null, 131 | "header": { 132 | "channel_header": { 133 | "channel_id": "", 134 | "epoch": "0", 135 | "extension": null, 136 | "timestamp": null, 137 | "tls_cert_hash": "", 138 | "tx_id": "", 139 | "type": 1, 140 | "version": 0 141 | }, 142 | "signature_header": null 143 | } 144 | }, 145 | "signature": "YmFy" 146 | } 147 | ] 148 | }, 149 | "header": { 150 | "data_hash": "", 151 | "number": "0", 152 | "previous_hash": "Zm9v" 153 | }, 154 | "metadata": null 155 | } 156 | `)) 157 | } 158 | 159 | func TestProposalResponsePayload(t *testing.T) { 160 | gt := NewGomegaWithT(t) 161 | 162 | prp := &pb.ProposalResponsePayload{} 163 | err := protolator.DeepUnmarshalJSON(bytes.NewReader([]byte(`{ 164 | "extension": { 165 | "chaincode_id": { 166 | "name": "test", 167 | "path": "", 168 | "version": "1.0" 169 | }, 170 | "events": { 171 | "chaincode_id": "test" 172 | }, 173 | "response": { 174 | "message": "", 175 | "payload": null, 176 | "status": 200 177 | }, 178 | "results": { 179 | "data_model": "KV", 180 | "ns_rwset": [ 181 | { 182 | "collection_hashed_rwset": [], 183 | "namespace": "lscc", 184 | "rwset": { 185 | "metadata_writes": [], 186 | "range_queries_info": [], 187 | "reads": [ 188 | { 189 | "key": "cc1", 190 | "version": { 191 | "block_num": "3", 192 | "tx_num": "0" 193 | } 194 | }, 195 | { 196 | "key": "cc2", 197 | "version": { 198 | "block_num": "4", 199 | "tx_num": "0" 200 | } 201 | } 202 | ], 203 | "writes": [] 204 | } 205 | }, 206 | { 207 | "collection_hashed_rwset": [], 208 | "namespace": "cc1", 209 | "rwset": { 210 | "metadata_writes": [], 211 | "range_queries_info": [], 212 | "reads": [ 213 | { 214 | "key": "key1", 215 | "version": { 216 | "block_num": "8", 217 | "tx_num": "0" 218 | } 219 | } 220 | ], 221 | "writes": [ 222 | { 223 | "is_delete": false, 224 | "key": "key2" 225 | } 226 | ] 227 | } 228 | }, 229 | { 230 | "collection_hashed_rwset": [], 231 | "namespace": "cc2", 232 | "rwset": { 233 | "metadata_writes": [], 234 | "range_queries_info": [], 235 | "reads": [ 236 | { 237 | "key": "key1", 238 | "version": { 239 | "block_num": "9", 240 | "tx_num": "0" 241 | } 242 | }, 243 | { 244 | "key": "key2", 245 | "version": { 246 | "block_num": "10", 247 | "tx_num": "0" 248 | } 249 | } 250 | ], 251 | "writes": [ 252 | { 253 | "is_delete": false, 254 | "key": "key1" 255 | }, 256 | { 257 | "is_delete": true, 258 | "key": "key2" 259 | } 260 | ] 261 | } 262 | } 263 | ] 264 | } 265 | } 266 | }`)), prp) 267 | gt.Expect(err).NotTo(HaveOccurred()) 268 | bidirectionalMarshal(t, prp) 269 | } 270 | 271 | func TestChannelCreationPolicy(t *testing.T) { 272 | cu := &cb.ConfigUpdate{ 273 | WriteSet: &cb.ConfigGroup{ 274 | Groups: map[string]*cb.ConfigGroup{ 275 | "Consortiums": { 276 | Groups: map[string]*cb.ConfigGroup{ 277 | "SampleConsortium": { 278 | Values: map[string]*cb.ConfigValue{ 279 | "ChannelCreationPolicy": { 280 | Version: 0, 281 | }, 282 | }, 283 | }, 284 | }, 285 | }, 286 | }, 287 | }, 288 | } 289 | 290 | bidirectionalMarshal(t, cu) 291 | } 292 | 293 | func TestStaticMarshal(t *testing.T) { 294 | gt := NewGomegaWithT(t) 295 | 296 | // To generate artifacts: 297 | // e.g. 298 | // FABRICPATH=$GOPATH/src/github.com/hyperledger/fabric 299 | // configtxgen -channelID test -outputBlock block.pb -profile SampleSingleMSPSolo -configPath FABRICPATH/sampleconfig 300 | // configtxgen -configPath FABRICPATH/sampleconfig -inspectBlock block.pb > block.json 301 | 302 | blockBin, err := os.ReadFile("testdata/block.pb") 303 | gt.Expect(err).NotTo(HaveOccurred()) 304 | 305 | block := &cb.Block{} 306 | err = proto.Unmarshal(blockBin, block) 307 | gt.Expect(err).NotTo(HaveOccurred()) 308 | 309 | jsonBin, err := os.ReadFile("testdata/block.json") 310 | gt.Expect(err).NotTo(HaveOccurred()) 311 | 312 | buf := &bytes.Buffer{} 313 | err = protolator.DeepMarshalJSON(buf, block) 314 | gt.Expect(err).NotTo(HaveOccurred()) 315 | gt.Expect(buf).To(MatchJSON(jsonBin)) 316 | } 317 | 318 | // protoMarshalOrPanic serializes a protobuf message and panics if this 319 | // operation fails 320 | func protoMarshalOrPanic(pb proto.Message) []byte { 321 | data, err := proto.Marshal(pb) 322 | if err != nil { 323 | panic(err) 324 | } 325 | 326 | return data 327 | } 328 | -------------------------------------------------------------------------------- /protolator/integration/testdata/block.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-config/df5311af8a6720d96941e03856a0bd6d5d345053/protolator/integration/testdata/block.pb -------------------------------------------------------------------------------- /protolator/json_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | "math" 24 | "reflect" 25 | "testing" 26 | 27 | "github.com/hyperledger/fabric-config/protolator/testprotos" 28 | . "github.com/onsi/gomega" 29 | "google.golang.org/protobuf/proto" 30 | ) 31 | 32 | type testProtoPlainFieldFactory struct { 33 | fromPrefix string 34 | toPrefix string 35 | fromError error 36 | toError error 37 | } 38 | 39 | func (tpff *testProtoPlainFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 40 | return fieldName == "plain_field" 41 | } 42 | 43 | func (tpff *testProtoPlainFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 44 | return &plainField{ 45 | baseField: baseField{ 46 | msg: msg, 47 | name: fieldName, 48 | fType: reflect.TypeOf(""), 49 | vType: fieldType, 50 | value: fieldValue, 51 | }, 52 | populateFrom: func(source interface{}, destType reflect.Type) (reflect.Value, error) { 53 | sourceAsString := source.(string) 54 | return reflect.ValueOf(tpff.fromPrefix + sourceAsString), tpff.fromError 55 | }, 56 | populateTo: func(source reflect.Value) (interface{}, error) { 57 | return tpff.toPrefix + source.Interface().(string), tpff.toError 58 | }, 59 | }, nil 60 | } 61 | 62 | func TestSimpleMsgPlainField(t *testing.T) { 63 | gt := NewGomegaWithT(t) 64 | 65 | fromPrefix := "from" 66 | toPrefix := "to" 67 | tppff := &testProtoPlainFieldFactory{ 68 | fromPrefix: fromPrefix, 69 | toPrefix: toPrefix, 70 | } 71 | 72 | fieldFactories = []protoFieldFactory{tppff} 73 | 74 | pfValue := "foo" 75 | startMsg := &testprotos.SimpleMsg{ 76 | PlainField: pfValue, 77 | MapField: map[string]string{"1": "2"}, 78 | SliceField: []string{"a", "b"}, 79 | } 80 | 81 | var buffer bytes.Buffer 82 | err := DeepMarshalJSON(&buffer, startMsg) 83 | gt.Expect(err).NotTo(HaveOccurred()) 84 | 85 | newMsg := &testprotos.SimpleMsg{} 86 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 87 | gt.Expect(err).NotTo(HaveOccurred()) 88 | 89 | gt.Expect(newMsg.MapField).To(Equal(startMsg.MapField)) 90 | gt.Expect(newMsg.SliceField).To(Equal(startMsg.SliceField)) 91 | gt.Expect(newMsg.PlainField).To(Equal(fromPrefix + toPrefix + startMsg.PlainField)) 92 | 93 | tppff.fromError = fmt.Errorf("Failing from intentionally") 94 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 95 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateFrom for field plain_field for message *testprotos.SimpleMsg: Failing from intentionally")) 96 | 97 | tppff.toError = fmt.Errorf("Failing to intentionally") 98 | err = DeepMarshalJSON(&buffer, startMsg) 99 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateTo for field plain_field for message *testprotos.SimpleMsg: Failing to intentionally")) 100 | } 101 | 102 | type testProtoMapFieldFactory struct { 103 | fromPrefix string 104 | toPrefix string 105 | fromError error 106 | toError error 107 | } 108 | 109 | func (tpff *testProtoMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 110 | return fieldName == "map_field" 111 | } 112 | 113 | func (tpff *testProtoMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 114 | return &mapField{ 115 | baseField: baseField{ 116 | msg: msg, 117 | name: fieldName, 118 | fType: reflect.TypeOf(""), 119 | vType: fieldType, 120 | value: fieldValue, 121 | }, 122 | populateFrom: func(key string, source interface{}, destType reflect.Type) (reflect.Value, error) { 123 | sourceAsString := source.(string) 124 | return reflect.ValueOf(tpff.fromPrefix + key + sourceAsString), tpff.fromError 125 | }, 126 | populateTo: func(key string, source reflect.Value) (interface{}, error) { 127 | return tpff.toPrefix + key + source.Interface().(string), tpff.toError 128 | }, 129 | }, nil 130 | } 131 | 132 | func TestSimpleMsgMapField(t *testing.T) { 133 | gt := NewGomegaWithT(t) 134 | 135 | fromPrefix := "from" 136 | toPrefix := "to" 137 | tpmff := &testProtoMapFieldFactory{ 138 | fromPrefix: fromPrefix, 139 | toPrefix: toPrefix, 140 | } 141 | fieldFactories = []protoFieldFactory{tpmff} 142 | 143 | key := "foo" 144 | value := "bar" 145 | startMsg := &testprotos.SimpleMsg{ 146 | PlainField: "1", 147 | MapField: map[string]string{key: value}, 148 | SliceField: []string{"a", "b"}, 149 | } 150 | 151 | var buffer bytes.Buffer 152 | err := DeepMarshalJSON(&buffer, startMsg) 153 | gt.Expect(err).NotTo(HaveOccurred()) 154 | 155 | newMsg := &testprotos.SimpleMsg{} 156 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 157 | gt.Expect(err).NotTo(HaveOccurred()) 158 | 159 | gt.Expect(newMsg.PlainField).To(Equal(startMsg.PlainField)) 160 | gt.Expect(newMsg.SliceField).To(Equal(startMsg.SliceField)) 161 | gt.Expect(newMsg.MapField[key]).To(Equal(fromPrefix + key + toPrefix + key + startMsg.MapField[key])) 162 | 163 | tpmff.fromError = fmt.Errorf("Failing from intentionally") 164 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 165 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateFrom for map field map_field with key foo for message *testprotos.SimpleMsg: Failing from intentionally")) 166 | 167 | tpmff.toError = fmt.Errorf("Failing to intentionally") 168 | err = DeepMarshalJSON(&buffer, startMsg) 169 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateTo for map field map_field and key foo for message *testprotos.SimpleMsg: Failing to intentionally")) 170 | } 171 | 172 | type testProtoSliceFieldFactory struct { 173 | fromPrefix string 174 | toPrefix string 175 | fromError error 176 | toError error 177 | } 178 | 179 | func (tpff *testProtoSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 180 | return fieldName == "slice_field" 181 | } 182 | 183 | func (tpff *testProtoSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 184 | return &sliceField{ 185 | baseField: baseField{ 186 | msg: msg, 187 | name: fieldName, 188 | fType: reflect.TypeOf(""), 189 | vType: fieldType, 190 | value: fieldValue, 191 | }, 192 | populateFrom: func(index int, source interface{}, destType reflect.Type) (reflect.Value, error) { 193 | sourceAsString := source.(string) 194 | return reflect.ValueOf(tpff.fromPrefix + fmt.Sprintf("%d", index) + sourceAsString), tpff.fromError 195 | }, 196 | populateTo: func(index int, source reflect.Value) (interface{}, error) { 197 | return tpff.toPrefix + fmt.Sprintf("%d", index) + source.Interface().(string), tpff.toError 198 | }, 199 | }, nil 200 | } 201 | 202 | func TestSimpleMsgSliceField(t *testing.T) { 203 | gt := NewGomegaWithT(t) 204 | 205 | fromPrefix := "from" 206 | toPrefix := "to" 207 | tpsff := &testProtoSliceFieldFactory{ 208 | fromPrefix: fromPrefix, 209 | toPrefix: toPrefix, 210 | } 211 | fieldFactories = []protoFieldFactory{tpsff} 212 | 213 | value := "foo" 214 | startMsg := &testprotos.SimpleMsg{ 215 | PlainField: "1", 216 | MapField: map[string]string{"a": "b"}, 217 | SliceField: []string{value}, 218 | } 219 | 220 | var buffer bytes.Buffer 221 | err := DeepMarshalJSON(&buffer, startMsg) 222 | gt.Expect(err).NotTo(HaveOccurred()) 223 | 224 | newMsg := &testprotos.SimpleMsg{} 225 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 226 | gt.Expect(err).NotTo(HaveOccurred()) 227 | 228 | gt.Expect(newMsg.PlainField).To(Equal(startMsg.PlainField)) 229 | gt.Expect(newMsg.MapField).To(Equal(startMsg.MapField)) 230 | gt.Expect(newMsg.SliceField[0]).To(Equal(fromPrefix + "0" + toPrefix + "0" + startMsg.SliceField[0])) 231 | 232 | tpsff.fromError = fmt.Errorf("Failing from intentionally") 233 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 234 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateFrom for slice field slice_field at index 0 for message *testprotos.SimpleMsg: Failing from intentionally")) 235 | 236 | tpsff.toError = fmt.Errorf("Failing to intentionally") 237 | err = DeepMarshalJSON(&buffer, startMsg) 238 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: error in PopulateTo for slice field slice_field at index 0 for message *testprotos.SimpleMsg: Failing to intentionally")) 239 | } 240 | 241 | type testProtoFailFactory struct{} 242 | 243 | func (tpff testProtoFailFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 244 | return true 245 | } 246 | 247 | func (tpff testProtoFailFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 248 | return nil, fmt.Errorf("Intentionally failing") 249 | } 250 | 251 | func TestFailFactory(t *testing.T) { 252 | gt := NewGomegaWithT(t) 253 | 254 | fieldFactories = []protoFieldFactory{&testProtoFailFactory{}} 255 | 256 | var buffer bytes.Buffer 257 | err := DeepMarshalJSON(&buffer, &testprotos.SimpleMsg{}) 258 | gt.Expect(err).To(MatchError("*testprotos.SimpleMsg: Intentionally failing")) 259 | } 260 | 261 | func TestJSONUnmarshalMaxUint32(t *testing.T) { 262 | gt := NewGomegaWithT(t) 263 | 264 | fieldName := "numField" 265 | jsonString := fmt.Sprintf("{\"%s\":%d}", fieldName, math.MaxUint32) 266 | m, err := jsonToMap([]byte(jsonString)) 267 | gt.Expect(err).NotTo(HaveOccurred()) 268 | gt.Expect(m[fieldName]).To(BeAssignableToTypeOf(json.Number(""))) 269 | } 270 | 271 | func TestMostlyDeterministicMarshal(t *testing.T) { 272 | gt := NewGomegaWithT(t) 273 | 274 | multiKeyMap := &testprotos.SimpleMsg{ 275 | MapField: map[string]string{ 276 | "a": "b", 277 | "c": "d", 278 | "e": "f", 279 | "g": "h", 280 | "i": "j", 281 | "k": "l", 282 | "m": "n", 283 | "o": "p", 284 | "q": "r", 285 | "s": "t", 286 | "u": "v", 287 | "w": "x", 288 | "y": "z", 289 | }, 290 | } 291 | 292 | result, err := MostlyDeterministicMarshal(multiKeyMap) 293 | gt.Expect(err).NotTo(HaveOccurred()) 294 | gt.Expect(result).NotTo(BeNil()) 295 | 296 | // Golang map marshaling is non-deterministic by default, by marshaling 297 | // the same message with an embedded map multiple times, we should 298 | // detect a mismatch if the default behavior persists. Even with 3 map 299 | // elements, there is usually a mismatch within 2-3 iterations, so 13 300 | // entries and 10 iterations seems like a reasonable check. 301 | for i := 0; i < 10; i++ { 302 | newResult, err := MostlyDeterministicMarshal(multiKeyMap) 303 | gt.Expect(err).NotTo(HaveOccurred()) 304 | gt.Expect(newResult).To(Equal(result)) 305 | } 306 | 307 | unmarshaled := &testprotos.SimpleMsg{} 308 | err = proto.Unmarshal(result, unmarshaled) 309 | gt.Expect(err).NotTo(HaveOccurred()) 310 | gt.Expect(proto.Equal(unmarshaled, multiKeyMap)).To(BeTrue()) 311 | } 312 | -------------------------------------------------------------------------------- /protolator/nested.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "reflect" 21 | 22 | "google.golang.org/protobuf/proto" 23 | "google.golang.org/protobuf/types/known/timestamppb" 24 | ) 25 | 26 | func nestedFrom(value interface{}, destType reflect.Type) (reflect.Value, error) { 27 | tree := value.(map[string]interface{}) // Safe, already checked 28 | result := reflect.New(destType.Elem()) 29 | nMsg := result.Interface().(proto.Message) // Safe, already checked 30 | if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil { 31 | return reflect.Value{}, err 32 | } 33 | return result, nil 34 | } 35 | 36 | func nestedTo(value reflect.Value) (interface{}, error) { 37 | nMsg := value.Interface().(proto.Message) // Safe, already checked 38 | return recursivelyCreateTreeFromMessage(nMsg) 39 | } 40 | 41 | var timestampType = reflect.TypeOf(×tamppb.Timestamp{}) 42 | 43 | type nestedFieldFactory struct{} 44 | 45 | func (nff nestedFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 46 | // Note, we skip recursing into the field if it is a proto native timestamp, because there is other custom marshaling this conflicts with 47 | // this should probably be revisited more generally to prevent custom marshaling of 'well known messages' 48 | return fieldType.Kind() == reflect.Ptr && fieldType.AssignableTo(protoMsgType) && !fieldType.AssignableTo(timestampType) 49 | } 50 | 51 | func (nff nestedFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 52 | return &plainField{ 53 | baseField: baseField{ 54 | msg: msg, 55 | name: fieldName, 56 | fType: mapStringInterfaceType, 57 | vType: fieldType, 58 | value: fieldValue, 59 | }, 60 | populateFrom: nestedFrom, 61 | populateTo: nestedTo, 62 | }, nil 63 | } 64 | 65 | type nestedMapFieldFactory struct{} 66 | 67 | func (nmff nestedMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 68 | return fieldType.Kind() == reflect.Map && fieldType.Elem().AssignableTo(protoMsgType) && !fieldType.Elem().AssignableTo(timestampType) && fieldType.Key().Kind() == reflect.String 69 | } 70 | 71 | func (nmff nestedMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 72 | return &mapField{ 73 | baseField: baseField{ 74 | msg: msg, 75 | name: fieldName, 76 | fType: mapStringInterfaceType, 77 | vType: fieldType, 78 | value: fieldValue, 79 | }, 80 | populateFrom: func(k string, v interface{}, dT reflect.Type) (reflect.Value, error) { 81 | return nestedFrom(v, dT) 82 | }, 83 | populateTo: func(k string, v reflect.Value) (interface{}, error) { 84 | return nestedTo(v) 85 | }, 86 | }, nil 87 | } 88 | 89 | type nestedSliceFieldFactory struct{} 90 | 91 | func (nmff nestedSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 92 | return fieldType.Kind() == reflect.Slice && fieldType.Elem().AssignableTo(protoMsgType) && !fieldType.Elem().AssignableTo(timestampType) 93 | } 94 | 95 | func (nmff nestedSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 96 | return &sliceField{ 97 | baseField: baseField{ 98 | msg: msg, 99 | name: fieldName, 100 | fType: mapStringInterfaceType, 101 | vType: fieldType, 102 | value: fieldValue, 103 | }, 104 | populateFrom: func(i int, v interface{}, dT reflect.Type) (reflect.Value, error) { 105 | return nestedFrom(v, dT) 106 | }, 107 | populateTo: func(i int, v reflect.Value) (interface{}, error) { 108 | return nestedTo(v) 109 | }, 110 | }, nil 111 | } 112 | -------------------------------------------------------------------------------- /protolator/nested_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "bytes" 21 | "testing" 22 | 23 | "github.com/hyperledger/fabric-config/protolator/testprotos" 24 | 25 | . "github.com/onsi/gomega" 26 | ) 27 | 28 | func TestPlainNestedMsg(t *testing.T) { 29 | gt := NewGomegaWithT(t) 30 | 31 | fromPrefix := "from" 32 | toPrefix := "to" 33 | tppff := &testProtoPlainFieldFactory{ 34 | fromPrefix: fromPrefix, 35 | toPrefix: toPrefix, 36 | } 37 | 38 | fieldFactories = []protoFieldFactory{tppff} 39 | 40 | pfValue := "foo" 41 | startMsg := &testprotos.NestedMsg{ 42 | PlainNestedField: &testprotos.SimpleMsg{ 43 | PlainField: pfValue, 44 | }, 45 | } 46 | 47 | var buffer bytes.Buffer 48 | err := DeepMarshalJSON(&buffer, startMsg) 49 | gt.Expect(err).NotTo(HaveOccurred()) 50 | newMsg := &testprotos.NestedMsg{} 51 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 52 | gt.Expect(err).NotTo(HaveOccurred()) 53 | gt.Expect(newMsg.PlainNestedField.PlainField).NotTo(Equal(fromPrefix + toPrefix + startMsg.PlainNestedField.PlainField)) 54 | 55 | fieldFactories = []protoFieldFactory{tppff, nestedFieldFactory{}} 56 | 57 | buffer.Reset() 58 | err = DeepMarshalJSON(&buffer, startMsg) 59 | gt.Expect(err).NotTo(HaveOccurred()) 60 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 61 | gt.Expect(err).NotTo(HaveOccurred()) 62 | gt.Expect(newMsg.PlainNestedField.PlainField).To(Equal(fromPrefix + toPrefix + startMsg.PlainNestedField.PlainField)) 63 | } 64 | 65 | func TestMapNestedMsg(t *testing.T) { 66 | gt := NewGomegaWithT(t) 67 | 68 | fromPrefix := "from" 69 | toPrefix := "to" 70 | tppff := &testProtoPlainFieldFactory{ 71 | fromPrefix: fromPrefix, 72 | toPrefix: toPrefix, 73 | } 74 | 75 | fieldFactories = []protoFieldFactory{tppff} 76 | 77 | pfValue := "foo" 78 | mapKey := "bar" 79 | startMsg := &testprotos.NestedMsg{ 80 | MapNestedField: map[string]*testprotos.SimpleMsg{ 81 | mapKey: { 82 | PlainField: pfValue, 83 | }, 84 | }, 85 | } 86 | 87 | var buffer bytes.Buffer 88 | err := DeepMarshalJSON(&buffer, startMsg) 89 | gt.Expect(err).NotTo(HaveOccurred()) 90 | newMsg := &testprotos.NestedMsg{} 91 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 92 | gt.Expect(err).NotTo(HaveOccurred()) 93 | gt.Expect(newMsg.MapNestedField[mapKey].PlainField).NotTo(Equal(fromPrefix + toPrefix + startMsg.MapNestedField[mapKey].PlainField)) 94 | 95 | fieldFactories = []protoFieldFactory{tppff, nestedMapFieldFactory{}} 96 | 97 | buffer.Reset() 98 | err = DeepMarshalJSON(&buffer, startMsg) 99 | gt.Expect(err).NotTo(HaveOccurred()) 100 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 101 | gt.Expect(err).NotTo(HaveOccurred()) 102 | gt.Expect(newMsg.MapNestedField[mapKey].PlainField).To(Equal(fromPrefix + toPrefix + startMsg.MapNestedField[mapKey].PlainField)) 103 | } 104 | 105 | func TestSliceNestedMsg(t *testing.T) { 106 | gt := NewGomegaWithT(t) 107 | 108 | fromPrefix := "from" 109 | toPrefix := "to" 110 | tppff := &testProtoPlainFieldFactory{ 111 | fromPrefix: fromPrefix, 112 | toPrefix: toPrefix, 113 | } 114 | 115 | fieldFactories = []protoFieldFactory{tppff} 116 | 117 | pfValue := "foo" 118 | startMsg := &testprotos.NestedMsg{ 119 | SliceNestedField: []*testprotos.SimpleMsg{ 120 | { 121 | PlainField: pfValue, 122 | }, 123 | }, 124 | } 125 | 126 | var buffer bytes.Buffer 127 | err := DeepMarshalJSON(&buffer, startMsg) 128 | gt.Expect(err).NotTo(HaveOccurred()) 129 | newMsg := &testprotos.NestedMsg{} 130 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 131 | gt.Expect(err).NotTo(HaveOccurred()) 132 | gt.Expect(newMsg.SliceNestedField[0].PlainField).NotTo(Equal(fromPrefix + toPrefix + startMsg.SliceNestedField[0].PlainField)) 133 | 134 | fieldFactories = []protoFieldFactory{tppff, nestedSliceFieldFactory{}} 135 | 136 | buffer.Reset() 137 | err = DeepMarshalJSON(&buffer, startMsg) 138 | gt.Expect(err).NotTo(HaveOccurred()) 139 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 140 | gt.Expect(err).NotTo(HaveOccurred()) 141 | gt.Expect(newMsg.SliceNestedField[0].PlainField).To(Equal(fromPrefix + toPrefix + startMsg.SliceNestedField[0].PlainField)) 142 | } 143 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 14 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 15 | "google.golang.org/protobuf/proto" 16 | ) 17 | 18 | type Envelope struct{ *common.Envelope } 19 | 20 | func (e *Envelope) Underlying() proto.Message { 21 | return e.Envelope 22 | } 23 | 24 | func (e *Envelope) StaticallyOpaqueFields() []string { 25 | return []string{"payload"} 26 | } 27 | 28 | func (e *Envelope) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 29 | if name != e.StaticallyOpaqueFields()[0] { 30 | return nil, fmt.Errorf("not a marshaled field: %s", name) 31 | } 32 | return &common.Payload{}, nil 33 | } 34 | 35 | type Payload struct{ *common.Payload } 36 | 37 | func (p *Payload) Underlying() proto.Message { 38 | return p.Payload 39 | } 40 | 41 | func (p *Payload) VariablyOpaqueFields() []string { 42 | return []string{"data"} 43 | } 44 | 45 | func (p *Payload) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 46 | if name != p.VariablyOpaqueFields()[0] { 47 | return nil, fmt.Errorf("not a marshaled field: %s", name) 48 | } 49 | if p.Header == nil { 50 | return nil, fmt.Errorf("cannot determine payload type when header is missing") 51 | } 52 | ch := &common.ChannelHeader{} 53 | if err := proto.Unmarshal(p.Header.ChannelHeader, ch); err != nil { 54 | return nil, fmt.Errorf("corrupt channel header: %s", err) 55 | } 56 | 57 | switch ch.Type { 58 | case int32(common.HeaderType_CONFIG): 59 | return &common.ConfigEnvelope{}, nil 60 | case int32(common.HeaderType_ORDERER_TRANSACTION): 61 | return &common.Envelope{}, nil 62 | case int32(common.HeaderType_CONFIG_UPDATE): 63 | return &common.ConfigUpdateEnvelope{}, nil 64 | case int32(common.HeaderType_MESSAGE): 65 | // Only used by broadcast_msg sample client 66 | return &common.ConfigValue{}, nil 67 | case int32(common.HeaderType_ENDORSER_TRANSACTION): 68 | return &peer.Transaction{}, nil 69 | default: 70 | return nil, fmt.Errorf("decoding type %v is unimplemented", ch.Type) 71 | } 72 | } 73 | 74 | type ChannelHeader struct{ *common.ChannelHeader } 75 | 76 | func (ch *ChannelHeader) Underlying() proto.Message { 77 | return ch.ChannelHeader 78 | } 79 | 80 | func (ch *ChannelHeader) VariablyOpaqueFields() []string { 81 | return []string{"extension"} 82 | } 83 | 84 | func (ch *ChannelHeader) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 85 | if name != "extension" { 86 | return nil, fmt.Errorf("not an opaque field") 87 | } 88 | 89 | switch ch.Type { 90 | case int32(common.HeaderType_ENDORSER_TRANSACTION): 91 | return &peer.ChaincodeHeaderExtension{}, nil 92 | default: 93 | return nil, fmt.Errorf("channel header extension only valid for endorser transactions") 94 | } 95 | } 96 | 97 | type Header struct{ *common.Header } 98 | 99 | func (h *Header) Underlying() proto.Message { 100 | return h.Header 101 | } 102 | 103 | func (h *Header) StaticallyOpaqueFields() []string { 104 | return []string{"channel_header", "signature_header"} 105 | } 106 | 107 | func (h *Header) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 108 | switch name { 109 | case h.StaticallyOpaqueFields()[0]: // channel_header 110 | return &common.ChannelHeader{}, nil 111 | case h.StaticallyOpaqueFields()[1]: // signature_header 112 | return &common.SignatureHeader{}, nil 113 | default: 114 | return nil, fmt.Errorf("unknown header field: %s", name) 115 | } 116 | } 117 | 118 | type SignatureHeader struct{ *common.SignatureHeader } 119 | 120 | func (sh *SignatureHeader) Underlying() proto.Message { 121 | return sh.SignatureHeader 122 | } 123 | 124 | func (sh *SignatureHeader) StaticallyOpaqueFields() []string { 125 | return []string{"creator"} 126 | } 127 | 128 | func (sh *SignatureHeader) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 129 | switch name { 130 | case sh.StaticallyOpaqueFields()[0]: // creator 131 | return &msp.SerializedIdentity{}, nil 132 | default: 133 | return nil, fmt.Errorf("unknown header field: %s", name) 134 | } 135 | } 136 | 137 | type BlockData struct{ *common.BlockData } 138 | 139 | func (bd *BlockData) Underlying() proto.Message { 140 | return bd.BlockData 141 | } 142 | 143 | func (bd *BlockData) StaticallyOpaqueSliceFields() []string { 144 | return []string{"data"} 145 | } 146 | 147 | func (bd *BlockData) StaticallyOpaqueSliceFieldProto(fieldName string, index int) (proto.Message, error) { 148 | if fieldName != bd.StaticallyOpaqueSliceFields()[0] { 149 | return nil, fmt.Errorf("not an opaque slice field: %s", fieldName) 150 | } 151 | 152 | return &common.Envelope{}, nil 153 | } 154 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/common_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext 8 | 9 | import ( 10 | "testing" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | . "github.com/onsi/gomega" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | func TestCommonProtolator(t *testing.T) { 18 | gt := NewGomegaWithT(t) 19 | 20 | // Envelope 21 | env := &Envelope{Envelope: &common.Envelope{}} 22 | gt.Expect(env.StaticallyOpaqueFields()).To(Equal([]string{"payload"})) 23 | msg, err := env.StaticallyOpaqueFieldProto("badproto") 24 | gt.Expect(msg).To(BeNil()) 25 | gt.Expect(err).To(MatchError("not a marshaled field: badproto")) 26 | msg, err = env.StaticallyOpaqueFieldProto("payload") 27 | gt.Expect(err).NotTo(HaveOccurred()) 28 | gt.Expect(msg).To(Equal(&common.Payload{})) 29 | 30 | // Payload 31 | payload := &Payload{Payload: &common.Payload{}} 32 | gt.Expect(payload.VariablyOpaqueFields()).To(Equal([]string{"data"})) 33 | msg, err = payload.VariablyOpaqueFieldProto("badproto") 34 | gt.Expect(msg).To(BeNil()) 35 | gt.Expect(err).To(MatchError("not a marshaled field: badproto")) 36 | msg, err = payload.VariablyOpaqueFieldProto("data") 37 | gt.Expect(msg).To(BeNil()) 38 | gt.Expect(err).To(MatchError("cannot determine payload type when header is missing")) 39 | 40 | payload = &Payload{ 41 | Payload: &common.Payload{ 42 | Header: &common.Header{ 43 | ChannelHeader: []byte("badbytes"), 44 | }, 45 | }, 46 | } 47 | msg, err = payload.VariablyOpaqueFieldProto("data") 48 | gt.Expect(msg).To(BeNil()) 49 | gt.Expect(err.Error()).To(ContainSubstring("corrupt channel header: proto:")) 50 | gt.Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) 51 | 52 | ch := &common.ChannelHeader{ 53 | Type: int32(common.HeaderType_CONFIG), 54 | } 55 | chbytes, _ := proto.Marshal(ch) 56 | payload = &Payload{ 57 | Payload: &common.Payload{ 58 | Header: &common.Header{ 59 | ChannelHeader: chbytes, 60 | }, 61 | }, 62 | } 63 | msg, err = payload.VariablyOpaqueFieldProto("data") 64 | gt.Expect(msg).To(Equal(&common.ConfigEnvelope{})) 65 | gt.Expect(err).NotTo(HaveOccurred()) 66 | 67 | ch = &common.ChannelHeader{ 68 | Type: int32(common.HeaderType_CONFIG_UPDATE), 69 | } 70 | chbytes, _ = proto.Marshal(ch) 71 | payload = &Payload{ 72 | Payload: &common.Payload{ 73 | Header: &common.Header{ 74 | ChannelHeader: chbytes, 75 | }, 76 | }, 77 | } 78 | msg, err = payload.VariablyOpaqueFieldProto("data") 79 | gt.Expect(msg).To(Equal(&common.ConfigUpdateEnvelope{})) 80 | gt.Expect(err).NotTo(HaveOccurred()) 81 | 82 | ch = &common.ChannelHeader{ 83 | Type: int32(common.HeaderType_CHAINCODE_PACKAGE), 84 | } 85 | chbytes, _ = proto.Marshal(ch) 86 | payload = &Payload{ 87 | Payload: &common.Payload{ 88 | Header: &common.Header{ 89 | ChannelHeader: chbytes, 90 | }, 91 | }, 92 | } 93 | msg, err = payload.VariablyOpaqueFieldProto("data") 94 | gt.Expect(msg).To(BeNil()) 95 | gt.Expect(err).To(MatchError("decoding type 6 is unimplemented")) 96 | 97 | // Header 98 | var header *Header 99 | gt.Expect(header.StaticallyOpaqueFields()).To(Equal( 100 | []string{"channel_header", "signature_header"})) 101 | 102 | msg, err = header.StaticallyOpaqueFieldProto("badproto") 103 | gt.Expect(msg).To(BeNil()) 104 | gt.Expect(err).To(MatchError("unknown header field: badproto")) 105 | 106 | msg, err = header.StaticallyOpaqueFieldProto("channel_header") 107 | gt.Expect(msg).To(Equal(&common.ChannelHeader{})) 108 | gt.Expect(err).NotTo(HaveOccurred()) 109 | 110 | msg, err = header.StaticallyOpaqueFieldProto("signature_header") 111 | gt.Expect(msg).To(Equal(&common.SignatureHeader{})) 112 | gt.Expect(err).NotTo(HaveOccurred()) 113 | 114 | // BlockData 115 | var bd *BlockData 116 | gt.Expect(bd.StaticallyOpaqueSliceFields()).To(Equal([]string{"data"})) 117 | 118 | msg, err = bd.StaticallyOpaqueSliceFieldProto("badslice", 0) 119 | gt.Expect(msg).To(BeNil()) 120 | gt.Expect(err).To(MatchError("not an opaque slice field: badslice")) 121 | msg, err = bd.StaticallyOpaqueSliceFieldProto("data", 0) 122 | gt.Expect(msg).To(Equal(&common.Envelope{})) 123 | gt.Expect(err).NotTo(HaveOccurred()) 124 | } 125 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/commonext_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext_test 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/commonext" 12 | ) 13 | 14 | // ensure structs implement expected interfaces 15 | var ( 16 | _ protolator.StaticallyOpaqueFieldProto = &commonext.Envelope{} 17 | _ protolator.DecoratedProto = &commonext.Envelope{} 18 | _ protolator.VariablyOpaqueFieldProto = &commonext.Payload{} 19 | _ protolator.DecoratedProto = &commonext.Payload{} 20 | _ protolator.StaticallyOpaqueFieldProto = &commonext.Header{} 21 | _ protolator.DecoratedProto = &commonext.Header{} 22 | _ protolator.StaticallyOpaqueFieldProto = &commonext.SignatureHeader{} 23 | _ protolator.DecoratedProto = &commonext.SignatureHeader{} 24 | _ protolator.StaticallyOpaqueSliceFieldProto = &commonext.BlockData{} 25 | _ protolator.DecoratedProto = &commonext.BlockData{} 26 | 27 | _ protolator.StaticallyOpaqueFieldProto = &commonext.ConfigUpdateEnvelope{} 28 | _ protolator.DecoratedProto = &commonext.ConfigUpdateEnvelope{} 29 | _ protolator.StaticallyOpaqueFieldProto = &commonext.ConfigSignature{} 30 | _ protolator.DecoratedProto = &commonext.ConfigSignature{} 31 | _ protolator.DynamicFieldProto = &commonext.Config{} 32 | _ protolator.DecoratedProto = &commonext.Config{} 33 | _ protolator.StaticallyOpaqueMapFieldProto = &commonext.ConfigUpdate{} 34 | _ protolator.DecoratedProto = &commonext.ConfigUpdate{} 35 | 36 | _ protolator.DynamicMapFieldProto = &commonext.DynamicChannelGroup{} 37 | _ protolator.DecoratedProto = &commonext.DynamicChannelGroup{} 38 | _ protolator.StaticallyOpaqueFieldProto = &commonext.DynamicChannelConfigValue{} 39 | _ protolator.DecoratedProto = &commonext.DynamicChannelConfigValue{} 40 | _ protolator.DynamicMapFieldProto = &commonext.DynamicConsortiumsGroup{} 41 | _ protolator.DecoratedProto = &commonext.DynamicConsortiumsGroup{} 42 | _ protolator.DynamicMapFieldProto = &commonext.DynamicConsortiumGroup{} 43 | _ protolator.DecoratedProto = &commonext.DynamicConsortiumGroup{} 44 | _ protolator.VariablyOpaqueFieldProto = &commonext.DynamicConsortiumConfigValue{} 45 | _ protolator.DecoratedProto = &commonext.DynamicConsortiumConfigValue{} 46 | _ protolator.DynamicMapFieldProto = &commonext.DynamicConsortiumOrgGroup{} 47 | _ protolator.DecoratedProto = &commonext.DynamicConsortiumOrgGroup{} 48 | _ protolator.StaticallyOpaqueFieldProto = &commonext.DynamicConsortiumOrgConfigValue{} 49 | _ protolator.DecoratedProto = &commonext.DynamicConsortiumOrgConfigValue{} 50 | ) 51 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/configtx.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type ConfigUpdateEnvelope struct{ *common.ConfigUpdateEnvelope } 17 | 18 | func (cue *ConfigUpdateEnvelope) Underlying() proto.Message { 19 | return cue.ConfigUpdateEnvelope 20 | } 21 | 22 | func (cue *ConfigUpdateEnvelope) StaticallyOpaqueFields() []string { 23 | return []string{"config_update"} 24 | } 25 | 26 | func (cue *ConfigUpdateEnvelope) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 27 | if name != cue.StaticallyOpaqueFields()[0] { 28 | return nil, fmt.Errorf("Not a marshaled field: %s", name) 29 | } 30 | return &common.ConfigUpdate{}, nil 31 | } 32 | 33 | type ConfigSignature struct{ *common.ConfigSignature } 34 | 35 | func (cs *ConfigSignature) Underlying() proto.Message { 36 | return cs.ConfigSignature 37 | } 38 | 39 | func (cs *ConfigSignature) StaticallyOpaqueFields() []string { 40 | return []string{"signature_header"} 41 | } 42 | 43 | func (cs *ConfigSignature) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 44 | if name != cs.StaticallyOpaqueFields()[0] { 45 | return nil, fmt.Errorf("Not a marshaled field: %s", name) 46 | } 47 | return &common.SignatureHeader{}, nil 48 | } 49 | 50 | type Config struct{ *common.Config } 51 | 52 | func (c *Config) Underlying() proto.Message { 53 | return c.Config 54 | } 55 | 56 | func (c *Config) DynamicFields() []string { 57 | return []string{"channel_group"} 58 | } 59 | 60 | func (c *Config) DynamicFieldProto(name string, base proto.Message) (proto.Message, error) { 61 | if name != c.DynamicFields()[0] { 62 | return nil, fmt.Errorf("Not a dynamic field: %s", name) 63 | } 64 | 65 | cg, ok := base.(*common.ConfigGroup) 66 | if !ok { 67 | return nil, fmt.Errorf("Config must embed a config group as its dynamic field") 68 | } 69 | 70 | return &DynamicChannelGroup{ConfigGroup: cg}, nil 71 | } 72 | 73 | // ConfigUpdateIsolatedDataTypes allows other proto packages to register types for 74 | // the isolated_data field. This is necessary to break import cycles. 75 | var ConfigUpdateIsolatedDataTypes = map[string]func(string) proto.Message{} 76 | 77 | type ConfigUpdate struct{ *common.ConfigUpdate } 78 | 79 | func (c *ConfigUpdate) Underlying() proto.Message { 80 | return c.ConfigUpdate 81 | } 82 | 83 | func (c *ConfigUpdate) StaticallyOpaqueMapFields() []string { 84 | return []string{"isolated_data"} 85 | } 86 | 87 | func (c *ConfigUpdate) StaticallyOpaqueMapFieldProto(name string, key string) (proto.Message, error) { 88 | if name != c.StaticallyOpaqueMapFields()[0] { 89 | return nil, fmt.Errorf("Not a statically opaque map field: %s", name) 90 | } 91 | 92 | mf, ok := ConfigUpdateIsolatedDataTypes[key] 93 | if !ok { 94 | return nil, fmt.Errorf("Unknown map key: %s", key) 95 | } 96 | 97 | return mf(key), nil 98 | } 99 | 100 | func (c *ConfigUpdate) DynamicFields() []string { 101 | return []string{"read_set", "write_set"} 102 | } 103 | 104 | func (c *ConfigUpdate) DynamicFieldProto(name string, base proto.Message) (proto.Message, error) { 105 | if name != c.DynamicFields()[0] && name != c.DynamicFields()[1] { 106 | return nil, fmt.Errorf("Not a dynamic field: %s", name) 107 | } 108 | 109 | cg, ok := base.(*common.ConfigGroup) 110 | if !ok { 111 | return nil, fmt.Errorf("Expected base to be *ConfigGroup, got %T", base) 112 | } 113 | 114 | return &DynamicChannelGroup{ConfigGroup: cg}, nil 115 | } 116 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-config/protolator/protoext/ordererext" 13 | "github.com/hyperledger/fabric-config/protolator/protoext/peerext" 14 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 15 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 16 | "google.golang.org/protobuf/proto" 17 | ) 18 | 19 | type DynamicChannelGroup struct { 20 | *common.ConfigGroup 21 | } 22 | 23 | func (dcg *DynamicChannelGroup) DynamicMapFields() []string { 24 | return []string{"values", "groups"} 25 | } 26 | 27 | func (dcg *DynamicChannelGroup) Underlying() proto.Message { 28 | return dcg.ConfigGroup 29 | } 30 | 31 | func (dcg *DynamicChannelGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 32 | switch name { 33 | case "groups": 34 | cg, ok := base.(*common.ConfigGroup) 35 | if !ok { 36 | return nil, fmt.Errorf("ConfigGroup groups can only contain ConfigGroup messages") 37 | } 38 | 39 | switch key { 40 | case "Consortiums": 41 | return &DynamicConsortiumsGroup{ConfigGroup: cg}, nil 42 | case "Orderer": 43 | return &ordererext.DynamicOrdererGroup{ConfigGroup: cg}, nil 44 | case "Application": 45 | return &peerext.DynamicApplicationGroup{ConfigGroup: cg}, nil 46 | default: 47 | return nil, fmt.Errorf("unknown channel group sub-group '%s'", key) 48 | } 49 | case "values": 50 | cv, ok := base.(*common.ConfigValue) 51 | if !ok { 52 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 53 | } 54 | return &DynamicChannelConfigValue{ 55 | ConfigValue: cv, 56 | name: key, 57 | }, nil 58 | default: 59 | return nil, fmt.Errorf("ConfigGroup does not have a dynamic field: %s", name) 60 | } 61 | } 62 | 63 | type DynamicChannelConfigValue struct { 64 | *common.ConfigValue 65 | name string 66 | } 67 | 68 | func (dccv *DynamicChannelConfigValue) StaticallyOpaqueFields() []string { 69 | return []string{"value"} 70 | } 71 | 72 | func (dccv *DynamicChannelConfigValue) Underlying() proto.Message { 73 | return dccv.ConfigValue 74 | } 75 | 76 | func (dccv *DynamicChannelConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 77 | if name != "value" { 78 | return nil, fmt.Errorf("not a marshaled field: %s", name) 79 | } 80 | switch dccv.name { 81 | case "HashingAlgorithm": 82 | return &common.HashingAlgorithm{}, nil 83 | case "BlockDataHashingStructure": 84 | return &common.BlockDataHashingStructure{}, nil 85 | case "OrdererAddresses": 86 | return &common.OrdererAddresses{}, nil 87 | case "Consortium": 88 | return &common.Consortium{}, nil 89 | case "Capabilities": 90 | return &common.Capabilities{}, nil 91 | default: 92 | return nil, fmt.Errorf("unknown Channel ConfigValue name: %s", dccv.name) 93 | } 94 | } 95 | 96 | type DynamicConsortiumsGroup struct { 97 | *common.ConfigGroup 98 | } 99 | 100 | func (dcg *DynamicConsortiumsGroup) Underlying() proto.Message { 101 | return dcg.ConfigGroup 102 | } 103 | 104 | func (dcg *DynamicConsortiumsGroup) DynamicMapFields() []string { 105 | return []string{"values", "groups"} 106 | } 107 | 108 | func (dcg *DynamicConsortiumsGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 109 | switch name { 110 | case "groups": 111 | cg, ok := base.(*common.ConfigGroup) 112 | if !ok { 113 | return nil, fmt.Errorf("ConfigGroup groups can only contain ConfigGroup messages") 114 | } 115 | 116 | return &DynamicConsortiumGroup{ 117 | ConfigGroup: cg, 118 | }, nil 119 | case "values": 120 | return nil, fmt.Errorf("Consortiums currently support no config values") 121 | default: 122 | return nil, fmt.Errorf("ConfigGroup does not have a dynamic field: %s", name) 123 | } 124 | } 125 | 126 | type DynamicConsortiumGroup struct { 127 | *common.ConfigGroup 128 | } 129 | 130 | func (dcg *DynamicConsortiumGroup) Underlying() proto.Message { 131 | return dcg.ConfigGroup 132 | } 133 | 134 | func (dcg *DynamicConsortiumGroup) DynamicMapFields() []string { 135 | return []string{"values", "groups"} 136 | } 137 | 138 | func (dcg *DynamicConsortiumGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 139 | switch name { 140 | case "groups": 141 | cg, ok := base.(*common.ConfigGroup) 142 | if !ok { 143 | return nil, fmt.Errorf("ConfigGroup groups can only contain ConfigGroup messages") 144 | } 145 | return &DynamicConsortiumOrgGroup{ 146 | ConfigGroup: cg, 147 | }, nil 148 | case "values": 149 | cv, ok := base.(*common.ConfigValue) 150 | if !ok { 151 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 152 | } 153 | 154 | return &DynamicConsortiumConfigValue{ 155 | ConfigValue: cv, 156 | name: key, 157 | }, nil 158 | default: 159 | return nil, fmt.Errorf("not a dynamic orderer map field: %s", name) 160 | } 161 | } 162 | 163 | type DynamicConsortiumConfigValue struct { 164 | *common.ConfigValue 165 | name string 166 | } 167 | 168 | func (dccv *DynamicConsortiumConfigValue) Underlying() proto.Message { 169 | return dccv.ConfigValue 170 | } 171 | 172 | func (dccv *DynamicConsortiumConfigValue) VariablyOpaqueFields() []string { 173 | return []string{"value"} 174 | } 175 | 176 | func (dccv *DynamicConsortiumConfigValue) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 177 | if name != "value" { 178 | return nil, fmt.Errorf("not a marshaled field: %s", name) 179 | } 180 | switch dccv.name { 181 | case "ChannelCreationPolicy": 182 | return &common.Policy{}, nil 183 | default: 184 | return nil, fmt.Errorf("unknown Consortium ConfigValue name: %s", dccv.name) 185 | } 186 | } 187 | 188 | type DynamicConsortiumOrgGroup struct { 189 | *common.ConfigGroup 190 | } 191 | 192 | func (dcg *DynamicConsortiumOrgGroup) Underlying() proto.Message { 193 | return dcg.ConfigGroup 194 | } 195 | 196 | func (dcg *DynamicConsortiumOrgGroup) DynamicMapFields() []string { 197 | return []string{"groups", "values"} 198 | } 199 | 200 | func (dcg *DynamicConsortiumOrgGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 201 | switch name { 202 | case "groups": 203 | return nil, fmt.Errorf("ConsortiumOrg groups do not support sub groups") 204 | case "values": 205 | cv, ok := base.(*common.ConfigValue) 206 | if !ok { 207 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 208 | } 209 | 210 | return &DynamicConsortiumOrgConfigValue{ 211 | ConfigValue: cv, 212 | name: key, 213 | }, nil 214 | default: 215 | return nil, fmt.Errorf("not a dynamic orderer map field: %s", name) 216 | } 217 | } 218 | 219 | type DynamicConsortiumOrgConfigValue struct { 220 | *common.ConfigValue 221 | name string 222 | } 223 | 224 | func (dcocv *DynamicConsortiumOrgConfigValue) Underlying() proto.Message { 225 | return dcocv.ConfigValue 226 | } 227 | 228 | func (dcocv *DynamicConsortiumOrgConfigValue) StaticallyOpaqueFields() []string { 229 | return []string{"value"} 230 | } 231 | 232 | func (dcocv *DynamicConsortiumOrgConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 233 | if name != "value" { 234 | return nil, fmt.Errorf("not a marshaled field: %s", name) 235 | } 236 | switch dcocv.name { 237 | case "MSP": 238 | return &msp.MSPConfig{}, nil 239 | default: 240 | return nil, fmt.Errorf("unknown Consortium Org ConfigValue name: %s", dcocv.name) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /protolator/protoext/commonext/policies.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package commonext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type Policy struct{ *common.Policy } 17 | 18 | func (p *Policy) Underlying() proto.Message { 19 | return p.Policy 20 | } 21 | 22 | func (p *Policy) VariablyOpaqueFields() []string { 23 | return []string{"value"} 24 | } 25 | 26 | func (p *Policy) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 27 | if name != p.VariablyOpaqueFields()[0] { 28 | return nil, fmt.Errorf("not a marshaled field: %s", name) 29 | } 30 | switch p.Type { 31 | case int32(common.Policy_SIGNATURE): 32 | return &common.SignaturePolicyEnvelope{}, nil 33 | case int32(common.Policy_IMPLICIT_META): 34 | return &common.ImplicitMetaPolicy{}, nil 35 | default: 36 | return nil, fmt.Errorf("unable to decode policy type: %v", p.Type) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /protolator/protoext/decorate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protoext 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator/protoext/commonext" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/ledger/rwsetext" 12 | "github.com/hyperledger/fabric-config/protolator/protoext/mspext" 13 | "github.com/hyperledger/fabric-config/protolator/protoext/ordererext" 14 | "github.com/hyperledger/fabric-config/protolator/protoext/peerext" 15 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 16 | "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" 17 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 18 | "github.com/hyperledger/fabric-protos-go-apiv2/orderer" 19 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | // Docorate will add additional capabilities to some protobuf messages that 24 | // enable proper JSON marshalling and unmarshalling in protolator. 25 | func Decorate(msg proto.Message) proto.Message { 26 | switch m := msg.(type) { 27 | case *common.BlockData: 28 | return &commonext.BlockData{BlockData: m} 29 | case *common.Config: 30 | return &commonext.Config{Config: m} 31 | case *common.ConfigSignature: 32 | return &commonext.ConfigSignature{ConfigSignature: m} 33 | case *common.ConfigUpdate: 34 | return &commonext.ConfigUpdate{ConfigUpdate: m} 35 | case *common.ConfigUpdateEnvelope: 36 | return &commonext.ConfigUpdateEnvelope{ConfigUpdateEnvelope: m} 37 | case *common.Envelope: 38 | return &commonext.Envelope{Envelope: m} 39 | case *common.Header: 40 | return &commonext.Header{Header: m} 41 | case *common.ChannelHeader: 42 | return &commonext.ChannelHeader{ChannelHeader: m} 43 | case *common.SignatureHeader: 44 | return &commonext.SignatureHeader{SignatureHeader: m} 45 | case *common.Payload: 46 | return &commonext.Payload{Payload: m} 47 | case *common.Policy: 48 | return &commonext.Policy{Policy: m} 49 | 50 | case *msp.MSPConfig: 51 | return &mspext.MSPConfig{MSPConfig: m} 52 | case *msp.MSPPrincipal: 53 | return &mspext.MSPPrincipal{MSPPrincipal: m} 54 | 55 | case *orderer.ConsensusType: 56 | return &ordererext.ConsensusType{ConsensusType: m} 57 | 58 | case *peer.ChaincodeAction: 59 | return &peerext.ChaincodeAction{ChaincodeAction: m} 60 | case *peer.ChaincodeActionPayload: 61 | return &peerext.ChaincodeActionPayload{ChaincodeActionPayload: m} 62 | case *peer.ChaincodeEndorsedAction: 63 | return &peerext.ChaincodeEndorsedAction{ChaincodeEndorsedAction: m} 64 | case *peer.ChaincodeProposalPayload: 65 | return &peerext.ChaincodeProposalPayload{ChaincodeProposalPayload: m} 66 | case *peer.ProposalResponsePayload: 67 | return &peerext.ProposalResponsePayload{ProposalResponsePayload: m} 68 | case *peer.TransactionAction: 69 | return &peerext.TransactionAction{TransactionAction: m} 70 | 71 | case *rwset.TxReadWriteSet: 72 | return &rwsetext.TxReadWriteSet{TxReadWriteSet: m} 73 | 74 | default: 75 | return msg 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /protolator/protoext/decorate_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protoext 8 | 9 | import ( 10 | "testing" 11 | 12 | "github.com/hyperledger/fabric-config/protolator/protoext/commonext" 13 | "github.com/hyperledger/fabric-config/protolator/protoext/ledger/rwsetext" 14 | "github.com/hyperledger/fabric-config/protolator/protoext/mspext" 15 | "github.com/hyperledger/fabric-config/protolator/protoext/ordererext" 16 | "github.com/hyperledger/fabric-config/protolator/protoext/peerext" 17 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 18 | "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" 19 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 20 | "github.com/hyperledger/fabric-protos-go-apiv2/orderer" 21 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 22 | . "github.com/onsi/gomega" 23 | "google.golang.org/protobuf/proto" 24 | "google.golang.org/protobuf/protoadapt" 25 | ) 26 | 27 | type GenericProtoMessage struct { 28 | GenericField string 29 | } 30 | 31 | func (g *GenericProtoMessage) Reset() { 32 | panic("not implemented") 33 | } 34 | 35 | func (g *GenericProtoMessage) String() string { 36 | return "not implemented" 37 | } 38 | 39 | func (g *GenericProtoMessage) ProtoMessage() { 40 | panic("not implemented") 41 | } 42 | 43 | func TestDecorate(t *testing.T) { 44 | tests := []struct { 45 | testSpec string 46 | msg proto.Message 47 | expectedReturn proto.Message 48 | }{ 49 | { 50 | testSpec: "common.BlockData", 51 | msg: &common.BlockData{ 52 | Data: [][]byte{ 53 | []byte("data-bytes"), 54 | }, 55 | }, 56 | expectedReturn: &commonext.BlockData{ 57 | BlockData: &common.BlockData{ 58 | Data: [][]byte{ 59 | []byte("data-bytes"), 60 | }, 61 | }, 62 | }, 63 | }, 64 | { 65 | testSpec: "common.Config", 66 | msg: &common.Config{ 67 | Sequence: 5, 68 | }, 69 | expectedReturn: &commonext.Config{ 70 | Config: &common.Config{ 71 | Sequence: 5, 72 | }, 73 | }, 74 | }, 75 | { 76 | testSpec: "common.ConfigSignature", 77 | msg: &common.ConfigSignature{ 78 | SignatureHeader: []byte("signature-header-bytes"), 79 | }, 80 | expectedReturn: &commonext.ConfigSignature{ 81 | ConfigSignature: &common.ConfigSignature{ 82 | SignatureHeader: []byte("signature-header-bytes"), 83 | }, 84 | }, 85 | }, 86 | { 87 | testSpec: "common.ConfigUpdate", 88 | msg: &common.ConfigUpdate{ 89 | ChannelId: "testchannel", 90 | }, 91 | expectedReturn: &commonext.ConfigUpdate{ 92 | ConfigUpdate: &common.ConfigUpdate{ 93 | ChannelId: "testchannel", 94 | }, 95 | }, 96 | }, 97 | { 98 | testSpec: "common.ConfigUpdateEnvelope", 99 | msg: &common.ConfigUpdateEnvelope{ 100 | ConfigUpdate: []byte("config-update-bytes"), 101 | }, 102 | expectedReturn: &commonext.ConfigUpdateEnvelope{ 103 | ConfigUpdateEnvelope: &common.ConfigUpdateEnvelope{ 104 | ConfigUpdate: []byte("config-update-bytes"), 105 | }, 106 | }, 107 | }, 108 | { 109 | testSpec: "common.Envelope", 110 | msg: &common.Envelope{ 111 | Payload: []byte("payload-bytes"), 112 | }, 113 | expectedReturn: &commonext.Envelope{ 114 | Envelope: &common.Envelope{ 115 | Payload: []byte("payload-bytes"), 116 | }, 117 | }, 118 | }, 119 | { 120 | testSpec: "common.Header", 121 | msg: &common.Header{ 122 | ChannelHeader: []byte("channel-header-bytes"), 123 | }, 124 | expectedReturn: &commonext.Header{ 125 | Header: &common.Header{ 126 | ChannelHeader: []byte("channel-header-bytes"), 127 | }, 128 | }, 129 | }, 130 | { 131 | testSpec: "common.ChannelHeader", 132 | msg: &common.ChannelHeader{ 133 | Type: 5, 134 | }, 135 | expectedReturn: &commonext.ChannelHeader{ 136 | ChannelHeader: &common.ChannelHeader{ 137 | Type: 5, 138 | }, 139 | }, 140 | }, 141 | { 142 | testSpec: "common.SignatureHeader", 143 | msg: &common.SignatureHeader{ 144 | Creator: []byte("creator-bytes"), 145 | }, 146 | expectedReturn: &commonext.SignatureHeader{ 147 | SignatureHeader: &common.SignatureHeader{ 148 | Creator: []byte("creator-bytes"), 149 | }, 150 | }, 151 | }, 152 | { 153 | testSpec: "common.Payload", 154 | msg: &common.Payload{ 155 | Header: &common.Header{ChannelHeader: []byte("channel-header-bytes")}, 156 | }, 157 | expectedReturn: &commonext.Payload{ 158 | Payload: &common.Payload{ 159 | Header: &common.Header{ChannelHeader: []byte("channel-header-bytes")}, 160 | }, 161 | }, 162 | }, 163 | { 164 | testSpec: "common.Policy", 165 | msg: &common.Policy{ 166 | Type: 5, 167 | }, 168 | expectedReturn: &commonext.Policy{ 169 | Policy: &common.Policy{ 170 | Type: 5, 171 | }, 172 | }, 173 | }, 174 | { 175 | testSpec: "msp.MSPConfig", 176 | msg: &msp.MSPConfig{ 177 | Type: 5, 178 | }, 179 | expectedReturn: &mspext.MSPConfig{ 180 | MSPConfig: &msp.MSPConfig{ 181 | Type: 5, 182 | }, 183 | }, 184 | }, 185 | { 186 | testSpec: "msp.MSPPrincipal", 187 | msg: &msp.MSPPrincipal{ 188 | Principal: []byte("principal-bytes"), 189 | }, 190 | expectedReturn: &mspext.MSPPrincipal{ 191 | MSPPrincipal: &msp.MSPPrincipal{ 192 | Principal: []byte("principal-bytes"), 193 | }, 194 | }, 195 | }, 196 | { 197 | testSpec: "orderer.ConsensusType", 198 | msg: &orderer.ConsensusType{ 199 | Type: "etcdraft", 200 | }, 201 | expectedReturn: &ordererext.ConsensusType{ 202 | ConsensusType: &orderer.ConsensusType{ 203 | Type: "etcdraft", 204 | }, 205 | }, 206 | }, 207 | { 208 | testSpec: "peer.ChaincodeAction", 209 | msg: &peer.ChaincodeAction{ 210 | Results: []byte("results-bytes"), 211 | }, 212 | expectedReturn: &peerext.ChaincodeAction{ 213 | ChaincodeAction: &peer.ChaincodeAction{ 214 | Results: []byte("results-bytes"), 215 | }, 216 | }, 217 | }, 218 | { 219 | testSpec: "peer.ChaincodeActionPayload", 220 | msg: &peer.ChaincodeActionPayload{ 221 | ChaincodeProposalPayload: []byte("chaincode-proposal-payload-bytes"), 222 | }, 223 | expectedReturn: &peerext.ChaincodeActionPayload{ 224 | ChaincodeActionPayload: &peer.ChaincodeActionPayload{ 225 | ChaincodeProposalPayload: []byte("chaincode-proposal-payload-bytes"), 226 | }, 227 | }, 228 | }, 229 | { 230 | testSpec: "peer.ChaincodeEndorsedAction", 231 | msg: &peer.ChaincodeEndorsedAction{ 232 | ProposalResponsePayload: []byte("proposal-response-payload-bytes"), 233 | }, 234 | expectedReturn: &peerext.ChaincodeEndorsedAction{ 235 | ChaincodeEndorsedAction: &peer.ChaincodeEndorsedAction{ 236 | ProposalResponsePayload: []byte("proposal-response-payload-bytes"), 237 | }, 238 | }, 239 | }, 240 | { 241 | testSpec: "peer.ChaincodeProposalPayload", 242 | msg: &peer.ChaincodeProposalPayload{ 243 | Input: []byte("input-bytes"), 244 | }, 245 | expectedReturn: &peerext.ChaincodeProposalPayload{ 246 | ChaincodeProposalPayload: &peer.ChaincodeProposalPayload{ 247 | Input: []byte("input-bytes"), 248 | }, 249 | }, 250 | }, 251 | { 252 | testSpec: "peer.ProposalResponsePayload", 253 | msg: &peer.ProposalResponsePayload{ 254 | ProposalHash: []byte("proposal-hash-bytes"), 255 | }, 256 | expectedReturn: &peerext.ProposalResponsePayload{ 257 | ProposalResponsePayload: &peer.ProposalResponsePayload{ 258 | ProposalHash: []byte("proposal-hash-bytes"), 259 | }, 260 | }, 261 | }, 262 | { 263 | testSpec: "peer.TransactionAction", 264 | msg: &peer.TransactionAction{ 265 | Header: []byte("header-bytes"), 266 | }, 267 | expectedReturn: &peerext.TransactionAction{ 268 | TransactionAction: &peer.TransactionAction{ 269 | Header: []byte("header-bytes"), 270 | }, 271 | }, 272 | }, 273 | { 274 | testSpec: "rwset.TxReadWriteSet", 275 | msg: &rwset.TxReadWriteSet{ 276 | NsRwset: []*rwset.NsReadWriteSet{ 277 | { 278 | Namespace: "namespace", 279 | }, 280 | }, 281 | }, 282 | expectedReturn: &rwsetext.TxReadWriteSet{ 283 | TxReadWriteSet: &rwset.TxReadWriteSet{ 284 | NsRwset: []*rwset.NsReadWriteSet{ 285 | { 286 | Namespace: "namespace", 287 | }, 288 | }, 289 | }, 290 | }, 291 | }, 292 | { 293 | testSpec: "default", 294 | msg: protoadapt.MessageV2Of(&GenericProtoMessage{ 295 | GenericField: "test", 296 | }), 297 | expectedReturn: protoadapt.MessageV2Of(&GenericProtoMessage{ 298 | GenericField: "test", 299 | }), 300 | }, 301 | } 302 | 303 | for _, tt := range tests { 304 | t.Run(tt.testSpec, func(t *testing.T) { 305 | gt := NewGomegaWithT(t) 306 | decoratedMsg := Decorate(tt.msg) 307 | gt.Expect(proto.Equal(decoratedMsg, tt.expectedReturn)).To(BeTrue()) 308 | }) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /protolator/protoext/ledger/rwsetext/rwset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package rwsetext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | type TxReadWriteSet struct{ *rwset.TxReadWriteSet } 18 | 19 | func (txrws *TxReadWriteSet) Underlying() proto.Message { 20 | return txrws.TxReadWriteSet 21 | } 22 | 23 | func (txrws *TxReadWriteSet) DynamicSliceFields() []string { 24 | if txrws.DataModel != rwset.TxReadWriteSet_KV { 25 | // We only know how to handle TxReadWriteSet_KV types 26 | return []string{} 27 | } 28 | 29 | return []string{"ns_rwset"} 30 | } 31 | 32 | func (txrws *TxReadWriteSet) DynamicSliceFieldProto(name string, index int, base proto.Message) (proto.Message, error) { 33 | if name != txrws.DynamicSliceFields()[0] { 34 | return nil, fmt.Errorf("Not a dynamic field: %s", name) 35 | } 36 | 37 | nsrw, ok := base.(*rwset.NsReadWriteSet) 38 | if !ok { 39 | return nil, fmt.Errorf("TxReadWriteSet must embed a NsReadWriteSet its dynamic field") 40 | } 41 | 42 | return &DynamicNsReadWriteSet{ 43 | NsReadWriteSet: nsrw, 44 | DataModel: txrws.DataModel, 45 | }, nil 46 | } 47 | 48 | type DynamicNsReadWriteSet struct { 49 | *rwset.NsReadWriteSet 50 | DataModel rwset.TxReadWriteSet_DataModel 51 | } 52 | 53 | func (dnrws *DynamicNsReadWriteSet) Underlying() proto.Message { 54 | return dnrws.NsReadWriteSet 55 | } 56 | 57 | func (dnrws *DynamicNsReadWriteSet) StaticallyOpaqueFields() []string { 58 | return []string{"rwset"} 59 | } 60 | 61 | func (dnrws *DynamicNsReadWriteSet) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 62 | switch name { 63 | case "rwset": 64 | switch dnrws.DataModel { 65 | case rwset.TxReadWriteSet_KV: 66 | return &kvrwset.KVRWSet{}, nil 67 | default: 68 | return nil, fmt.Errorf("unknown data model type: %v", dnrws.DataModel) 69 | } 70 | default: 71 | return nil, fmt.Errorf("not a marshaled field: %s", name) 72 | } 73 | } 74 | 75 | func (dnrws *DynamicNsReadWriteSet) DynamicSliceFields() []string { 76 | if dnrws.DataModel != rwset.TxReadWriteSet_KV { 77 | // We only know how to handle TxReadWriteSet_KV types 78 | return []string{} 79 | } 80 | 81 | return []string{"collection_hashed_rwset"} 82 | } 83 | 84 | func (dnrws *DynamicNsReadWriteSet) DynamicSliceFieldProto(name string, index int, base proto.Message) (proto.Message, error) { 85 | if name != dnrws.DynamicSliceFields()[0] { 86 | return nil, fmt.Errorf("Not a dynamic field: %s", name) 87 | } 88 | 89 | chrws, ok := base.(*rwset.CollectionHashedReadWriteSet) 90 | if !ok { 91 | return nil, fmt.Errorf("NsReadWriteSet must embed a *CollectionHashedReadWriteSet its dynamic field") 92 | } 93 | 94 | return &DynamicCollectionHashedReadWriteSet{ 95 | CollectionHashedReadWriteSet: chrws, 96 | DataModel: dnrws.DataModel, 97 | }, nil 98 | } 99 | 100 | type DynamicCollectionHashedReadWriteSet struct { 101 | *rwset.CollectionHashedReadWriteSet 102 | DataModel rwset.TxReadWriteSet_DataModel 103 | } 104 | 105 | func (dchrws *DynamicCollectionHashedReadWriteSet) Underlying() proto.Message { 106 | return dchrws.CollectionHashedReadWriteSet 107 | } 108 | 109 | func (dchrws *DynamicCollectionHashedReadWriteSet) StaticallyOpaqueFields() []string { 110 | return []string{"rwset"} 111 | } 112 | 113 | func (dchrws *DynamicCollectionHashedReadWriteSet) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 114 | switch name { 115 | case "rwset": 116 | switch dchrws.DataModel { 117 | case rwset.TxReadWriteSet_KV: 118 | return &kvrwset.HashedRWSet{}, nil 119 | default: 120 | return nil, fmt.Errorf("unknown data model type: %v", dchrws.DataModel) 121 | } 122 | default: 123 | return nil, fmt.Errorf("not a marshaled field: %s", name) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /protolator/protoext/ledger/rwsetext/rwsetext_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package rwsetext_test 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/ledger/rwsetext" 12 | ) 13 | 14 | // ensure structs implement expected interfaces 15 | var ( 16 | _ protolator.DynamicSliceFieldProto = &rwsetext.TxReadWriteSet{} 17 | _ protolator.DecoratedProto = &rwsetext.TxReadWriteSet{} 18 | _ protolator.StaticallyOpaqueFieldProto = &rwsetext.DynamicNsReadWriteSet{} 19 | _ protolator.DecoratedProto = &rwsetext.DynamicNsReadWriteSet{} 20 | _ protolator.StaticallyOpaqueFieldProto = &rwsetext.DynamicCollectionHashedReadWriteSet{} 21 | _ protolator.DecoratedProto = &rwsetext.DynamicCollectionHashedReadWriteSet{} 22 | ) 23 | -------------------------------------------------------------------------------- /protolator/protoext/mspext/msp_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package mspext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type MSPConfig struct{ *msp.MSPConfig } 17 | 18 | func (mc *MSPConfig) Underlying() proto.Message { 19 | return mc.MSPConfig 20 | } 21 | 22 | func (mc *MSPConfig) VariablyOpaqueFields() []string { 23 | return []string{"config"} 24 | } 25 | 26 | func (mc *MSPConfig) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 27 | if name != mc.VariablyOpaqueFields()[0] { 28 | return nil, fmt.Errorf("not a marshaled field: %s", name) 29 | } 30 | switch mc.Type { 31 | case 0: 32 | return &msp.FabricMSPConfig{}, nil 33 | case 1: 34 | return &msp.IdemixMSPConfig{}, nil 35 | default: 36 | return nil, fmt.Errorf("unable to decode MSP type: %v", mc.Type) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /protolator/protoext/mspext/msp_principal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package mspext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type MSPPrincipal struct{ *msp.MSPPrincipal } 17 | 18 | func (mp *MSPPrincipal) Underlying() proto.Message { 19 | return mp.MSPPrincipal 20 | } 21 | 22 | func (mp *MSPPrincipal) VariablyOpaqueFields() []string { 23 | return []string{"principal"} 24 | } 25 | 26 | func (mp *MSPPrincipal) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 27 | if name != mp.VariablyOpaqueFields()[0] { 28 | return nil, fmt.Errorf("not a marshaled field: %s", name) 29 | } 30 | switch mp.PrincipalClassification { 31 | case msp.MSPPrincipal_ROLE: 32 | return &msp.MSPRole{}, nil 33 | case msp.MSPPrincipal_ORGANIZATION_UNIT: 34 | return &msp.OrganizationUnit{}, nil 35 | case msp.MSPPrincipal_IDENTITY: 36 | return &msp.SerializedIdentity{}, nil 37 | default: 38 | return nil, fmt.Errorf("unable to decode MSP type: %v", mp.PrincipalClassification) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /protolator/protoext/mspext/mspext_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package mspext_test 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/mspext" 12 | ) 13 | 14 | // ensure structs implement expected interfaces 15 | var ( 16 | _ protolator.VariablyOpaqueFieldProto = &mspext.MSPConfig{} 17 | _ protolator.DecoratedProto = &mspext.MSPConfig{} 18 | 19 | _ protolator.VariablyOpaqueFieldProto = &mspext.MSPPrincipal{} 20 | _ protolator.DecoratedProto = &mspext.MSPPrincipal{} 21 | ) 22 | -------------------------------------------------------------------------------- /protolator/protoext/ordererext/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package ordererext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 14 | "github.com/hyperledger/fabric-protos-go-apiv2/orderer" 15 | "github.com/hyperledger/fabric-protos-go-apiv2/orderer/etcdraft" 16 | "github.com/hyperledger/fabric-protos-go-apiv2/orderer/smartbft" 17 | "google.golang.org/protobuf/proto" 18 | "google.golang.org/protobuf/types/known/emptypb" 19 | ) 20 | 21 | type DynamicOrdererGroup struct { 22 | *common.ConfigGroup 23 | } 24 | 25 | func (dcg *DynamicOrdererGroup) Underlying() proto.Message { 26 | return dcg.ConfigGroup 27 | } 28 | 29 | func (dcg *DynamicOrdererGroup) DynamicMapFields() []string { 30 | return []string{"values", "groups"} 31 | } 32 | 33 | func (dcg *DynamicOrdererGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 34 | switch name { 35 | case "groups": 36 | cg, ok := base.(*common.ConfigGroup) 37 | if !ok { 38 | return nil, fmt.Errorf("ConfigGroup groups can only contain ConfigGroup messages") 39 | } 40 | 41 | return &DynamicOrdererOrgGroup{ 42 | ConfigGroup: cg, 43 | }, nil 44 | case "values": 45 | cv, ok := base.(*common.ConfigValue) 46 | if !ok { 47 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 48 | } 49 | return &DynamicOrdererConfigValue{ 50 | ConfigValue: cv, 51 | name: key, 52 | }, nil 53 | default: 54 | return nil, fmt.Errorf("ConfigGroup does not have a dynamic field: %s", name) 55 | } 56 | } 57 | 58 | type ConsensusTypeMetadataFactory interface { 59 | NewMessage() proto.Message 60 | } 61 | 62 | // ConsensuTypeMetadataMap should have consensus implementations register their metadata message factories 63 | var ConsensusTypeMetadataMap = map[string]ConsensusTypeMetadataFactory{} 64 | 65 | type ConsensusType struct { 66 | *orderer.ConsensusType 67 | } 68 | 69 | func (ct *ConsensusType) Underlying() proto.Message { 70 | return ct.ConsensusType 71 | } 72 | 73 | func (ct *ConsensusType) VariablyOpaqueFields() []string { 74 | return []string{"metadata"} 75 | } 76 | 77 | func (ct *ConsensusType) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 78 | if name != "metadata" { 79 | return nil, fmt.Errorf("not a valid opaque field: %s", name) 80 | } 81 | switch ct.Type { 82 | case "etcdraft": 83 | return &etcdraft.ConfigMetadata{}, nil 84 | case "BFT": 85 | return &smartbft.Options{}, nil 86 | default: 87 | return &emptypb.Empty{}, nil 88 | } 89 | } 90 | 91 | type DynamicOrdererOrgGroup struct { 92 | *common.ConfigGroup 93 | } 94 | 95 | func (dcg *DynamicOrdererOrgGroup) Underlying() proto.Message { 96 | return dcg.ConfigGroup 97 | } 98 | 99 | func (dcg *DynamicOrdererOrgGroup) DynamicMapFields() []string { 100 | return []string{"groups", "values"} 101 | } 102 | 103 | func (dcg *DynamicOrdererOrgGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 104 | switch name { 105 | case "groups": 106 | return nil, fmt.Errorf("the orderer orgs do not support sub-groups") 107 | case "values": 108 | cv, ok := base.(*common.ConfigValue) 109 | if !ok { 110 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 111 | } 112 | 113 | return &DynamicOrdererOrgConfigValue{ 114 | ConfigValue: cv, 115 | name: key, 116 | }, nil 117 | default: 118 | return nil, fmt.Errorf("not a dynamic orderer map field: %s", name) 119 | } 120 | } 121 | 122 | type DynamicOrdererConfigValue struct { 123 | *common.ConfigValue 124 | name string 125 | } 126 | 127 | func (docv *DynamicOrdererConfigValue) Underlying() proto.Message { 128 | return docv.ConfigValue 129 | } 130 | 131 | func (docv *DynamicOrdererConfigValue) StaticallyOpaqueFields() []string { 132 | return []string{"value"} 133 | } 134 | 135 | func (docv *DynamicOrdererConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 136 | if name != "value" { 137 | return nil, fmt.Errorf("not a marshaled field: %s", name) 138 | } 139 | switch docv.name { 140 | case "ConsensusType": 141 | return &orderer.ConsensusType{}, nil 142 | case "BatchSize": 143 | return &orderer.BatchSize{}, nil 144 | case "BatchTimeout": 145 | return &orderer.BatchTimeout{}, nil 146 | case "KafkaBrokers": 147 | return &orderer.KafkaBrokers{}, nil 148 | case "ChannelRestrictions": 149 | return &orderer.ChannelRestrictions{}, nil 150 | case "Capabilities": 151 | return &common.Capabilities{}, nil 152 | case "Orderers": 153 | return &common.Orderers{}, nil 154 | default: 155 | return nil, fmt.Errorf("unknown Orderer ConfigValue name: %s", docv.name) 156 | } 157 | } 158 | 159 | type DynamicOrdererOrgConfigValue struct { 160 | *common.ConfigValue 161 | name string 162 | } 163 | 164 | func (doocv *DynamicOrdererOrgConfigValue) Underlying() proto.Message { 165 | return doocv.ConfigValue 166 | } 167 | 168 | func (doocv *DynamicOrdererOrgConfigValue) StaticallyOpaqueFields() []string { 169 | return []string{"value"} 170 | } 171 | 172 | func (doocv *DynamicOrdererOrgConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 173 | if name != "value" { 174 | return nil, fmt.Errorf("not a marshaled field: %s", name) 175 | } 176 | switch doocv.name { 177 | case "MSP": 178 | return &msp.MSPConfig{}, nil 179 | case "Endpoints": 180 | return &common.OrdererAddresses{}, nil 181 | default: 182 | return nil, fmt.Errorf("unknown Orderer Org ConfigValue name: %s", doocv.name) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /protolator/protoext/ordererext/ordererext_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package ordererext_test 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/ordererext" 12 | ) 13 | 14 | // ensure structs implement expected interfaces 15 | var ( 16 | _ protolator.DynamicMapFieldProto = &ordererext.DynamicOrdererGroup{} 17 | _ protolator.DecoratedProto = &ordererext.DynamicOrdererGroup{} 18 | _ protolator.VariablyOpaqueFieldProto = &ordererext.ConsensusType{} 19 | _ protolator.DecoratedProto = &ordererext.ConsensusType{} 20 | _ protolator.DynamicMapFieldProto = &ordererext.DynamicOrdererOrgGroup{} 21 | _ protolator.DecoratedProto = &ordererext.DynamicOrdererOrgGroup{} 22 | _ protolator.StaticallyOpaqueFieldProto = &ordererext.DynamicOrdererConfigValue{} 23 | _ protolator.DecoratedProto = &ordererext.DynamicOrdererConfigValue{} 24 | _ protolator.StaticallyOpaqueFieldProto = &ordererext.DynamicOrdererOrgConfigValue{} 25 | _ protolator.DecoratedProto = &ordererext.DynamicOrdererOrgConfigValue{} 26 | ) 27 | -------------------------------------------------------------------------------- /protolator/protoext/peerext/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package peerext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 14 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 15 | "google.golang.org/protobuf/proto" 16 | ) 17 | 18 | type DynamicApplicationGroup struct { 19 | *common.ConfigGroup 20 | } 21 | 22 | func (dag *DynamicApplicationGroup) Underlying() proto.Message { 23 | return dag.ConfigGroup 24 | } 25 | 26 | func (dag *DynamicApplicationGroup) DynamicMapFields() []string { 27 | return []string{"groups", "values"} 28 | } 29 | 30 | func (dag *DynamicApplicationGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 31 | switch name { 32 | case "groups": 33 | cg, ok := base.(*common.ConfigGroup) 34 | if !ok { 35 | return nil, fmt.Errorf("ConfigGroup groups can only contain ConfigGroup messages") 36 | } 37 | 38 | return &DynamicApplicationOrgGroup{ 39 | ConfigGroup: cg, 40 | }, nil 41 | case "values": 42 | cv, ok := base.(*common.ConfigValue) 43 | if !ok { 44 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 45 | } 46 | return &DynamicApplicationConfigValue{ 47 | ConfigValue: cv, 48 | name: key, 49 | }, nil 50 | default: 51 | return nil, fmt.Errorf("ConfigGroup does not have a dynamic field: %s", name) 52 | } 53 | } 54 | 55 | type DynamicApplicationOrgGroup struct { 56 | *common.ConfigGroup 57 | } 58 | 59 | func (dag *DynamicApplicationOrgGroup) Underlying() proto.Message { 60 | return dag.ConfigGroup 61 | } 62 | 63 | func (dag *DynamicApplicationOrgGroup) DynamicMapFields() []string { 64 | return []string{"groups", "values"} 65 | } 66 | 67 | func (dag *DynamicApplicationOrgGroup) DynamicMapFieldProto(name string, key string, base proto.Message) (proto.Message, error) { 68 | switch name { 69 | case "groups": 70 | return nil, fmt.Errorf("The application orgs do not support sub-groups") 71 | case "values": 72 | cv, ok := base.(*common.ConfigValue) 73 | if !ok { 74 | return nil, fmt.Errorf("ConfigGroup values can only contain ConfigValue messages") 75 | } 76 | 77 | return &DynamicApplicationOrgConfigValue{ 78 | ConfigValue: cv, 79 | name: key, 80 | }, nil 81 | default: 82 | return nil, fmt.Errorf("Not a dynamic application map field: %s", name) 83 | } 84 | } 85 | 86 | type DynamicApplicationConfigValue struct { 87 | *common.ConfigValue 88 | name string 89 | } 90 | 91 | func (ccv *DynamicApplicationConfigValue) Underlying() proto.Message { 92 | return ccv.ConfigValue 93 | } 94 | 95 | func (ccv *DynamicApplicationConfigValue) StaticallyOpaqueFields() []string { 96 | return []string{"value"} 97 | } 98 | 99 | func (ccv *DynamicApplicationConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 100 | if name != "value" { 101 | return nil, fmt.Errorf("Not a marshaled field: %s", name) 102 | } 103 | switch ccv.name { 104 | case "Capabilities": 105 | return &common.Capabilities{}, nil 106 | case "ACLs": 107 | return &peer.ACLs{}, nil 108 | default: 109 | return nil, fmt.Errorf("Unknown Application ConfigValue name: %s", ccv.name) 110 | } 111 | } 112 | 113 | type DynamicApplicationOrgConfigValue struct { 114 | *common.ConfigValue 115 | name string 116 | } 117 | 118 | func (daocv *DynamicApplicationOrgConfigValue) Underlying() proto.Message { 119 | return daocv.ConfigValue 120 | } 121 | 122 | func (daocv *DynamicApplicationOrgConfigValue) StaticallyOpaqueFields() []string { 123 | return []string{"value"} 124 | } 125 | 126 | func (daocv *DynamicApplicationOrgConfigValue) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 127 | if name != "value" { 128 | return nil, fmt.Errorf("Not a marshaled field: %s", name) 129 | } 130 | switch daocv.name { 131 | case "MSP": 132 | return &msp.MSPConfig{}, nil 133 | case "AnchorPeers": 134 | return &peer.AnchorPeers{}, nil 135 | default: 136 | return nil, fmt.Errorf("Unknown Application Org ConfigValue name: %s", daocv.name) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /protolator/protoext/peerext/peerext_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package peerext_test 8 | 9 | import ( 10 | "github.com/hyperledger/fabric-config/protolator" 11 | "github.com/hyperledger/fabric-config/protolator/protoext/peerext" 12 | ) 13 | 14 | // ensure structs implement expected interfaces 15 | var ( 16 | _ protolator.DynamicMapFieldProto = &peerext.DynamicApplicationGroup{} 17 | _ protolator.DecoratedProto = &peerext.DynamicApplicationGroup{} 18 | _ protolator.DynamicMapFieldProto = &peerext.DynamicApplicationOrgGroup{} 19 | _ protolator.DecoratedProto = &peerext.DynamicApplicationOrgGroup{} 20 | _ protolator.StaticallyOpaqueFieldProto = &peerext.DynamicApplicationConfigValue{} 21 | _ protolator.DecoratedProto = &peerext.DynamicApplicationConfigValue{} 22 | _ protolator.StaticallyOpaqueFieldProto = &peerext.DynamicApplicationOrgConfigValue{} 23 | _ protolator.DecoratedProto = &peerext.DynamicApplicationOrgConfigValue{} 24 | 25 | _ protolator.StaticallyOpaqueFieldProto = &peerext.ChaincodeProposalPayload{} 26 | _ protolator.DecoratedProto = &peerext.ChaincodeProposalPayload{} 27 | _ protolator.StaticallyOpaqueFieldProto = &peerext.ChaincodeAction{} 28 | _ protolator.DecoratedProto = &peerext.ChaincodeAction{} 29 | 30 | _ protolator.StaticallyOpaqueFieldProto = &peerext.ProposalResponsePayload{} 31 | _ protolator.DecoratedProto = &peerext.ProposalResponsePayload{} 32 | 33 | _ protolator.StaticallyOpaqueFieldProto = &peerext.TransactionAction{} 34 | _ protolator.DecoratedProto = &peerext.TransactionAction{} 35 | _ protolator.StaticallyOpaqueFieldProto = &peerext.ChaincodeActionPayload{} 36 | _ protolator.DecoratedProto = &peerext.ChaincodeActionPayload{} 37 | _ protolator.StaticallyOpaqueFieldProto = &peerext.ChaincodeEndorsedAction{} 38 | _ protolator.DecoratedProto = &peerext.ChaincodeEndorsedAction{} 39 | ) 40 | -------------------------------------------------------------------------------- /protolator/protoext/peerext/proposal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package peerext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | type ChaincodeProposalPayload struct { 18 | *peer.ChaincodeProposalPayload 19 | } 20 | 21 | func (cpp *ChaincodeProposalPayload) Underlying() proto.Message { 22 | return cpp.ChaincodeProposalPayload 23 | } 24 | 25 | func (cpp *ChaincodeProposalPayload) StaticallyOpaqueFields() []string { 26 | return []string{"input"} 27 | } 28 | 29 | func (cpp *ChaincodeProposalPayload) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 30 | if name != cpp.StaticallyOpaqueFields()[0] { 31 | return nil, fmt.Errorf("not a marshaled field: %s", name) 32 | } 33 | return &peer.ChaincodeInvocationSpec{}, nil 34 | } 35 | 36 | type ChaincodeAction struct { 37 | *peer.ChaincodeAction 38 | } 39 | 40 | func (ca *ChaincodeAction) Underlying() proto.Message { 41 | return ca.ChaincodeAction 42 | } 43 | 44 | func (ca *ChaincodeAction) StaticallyOpaqueFields() []string { 45 | return []string{"results", "events"} 46 | } 47 | 48 | func (ca *ChaincodeAction) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 49 | switch name { 50 | case "results": 51 | return &rwset.TxReadWriteSet{}, nil 52 | case "events": 53 | return &peer.ChaincodeEvent{}, nil 54 | default: 55 | return nil, fmt.Errorf("not a marshaled field: %s", name) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /protolator/protoext/peerext/proposal_response.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package peerext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type ProposalResponsePayload struct { 17 | *peer.ProposalResponsePayload 18 | } 19 | 20 | func (ppr *ProposalResponsePayload) Underlying() proto.Message { 21 | return ppr.ProposalResponsePayload 22 | } 23 | 24 | func (ppr *ProposalResponsePayload) StaticallyOpaqueFields() []string { 25 | return []string{"extension"} 26 | } 27 | 28 | func (ppr *ProposalResponsePayload) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 29 | if name != ppr.StaticallyOpaqueFields()[0] { 30 | return nil, fmt.Errorf("not a marshaled field: %s", name) 31 | } 32 | return &peer.ChaincodeAction{}, nil 33 | } 34 | -------------------------------------------------------------------------------- /protolator/protoext/peerext/transaction.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package peerext 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | type TransactionAction struct { // nothing was testing this 18 | *peer.TransactionAction 19 | } 20 | 21 | func (ta *TransactionAction) Underlying() proto.Message { 22 | return ta.TransactionAction 23 | } 24 | 25 | func (ta *TransactionAction) StaticallyOpaqueFields() []string { 26 | return []string{"header", "payload"} 27 | } 28 | 29 | func (ta *TransactionAction) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 30 | switch name { 31 | case ta.StaticallyOpaqueFields()[0]: 32 | return &common.SignatureHeader{}, nil 33 | case ta.StaticallyOpaqueFields()[1]: 34 | return &peer.ChaincodeActionPayload{}, nil 35 | default: 36 | return nil, fmt.Errorf("not a marshaled field: %s", name) 37 | } 38 | } 39 | 40 | type ChaincodeActionPayload struct { 41 | *peer.ChaincodeActionPayload 42 | } 43 | 44 | func (cap *ChaincodeActionPayload) Underlying() proto.Message { 45 | return cap.ChaincodeActionPayload 46 | } 47 | 48 | func (cap *ChaincodeActionPayload) StaticallyOpaqueFields() []string { 49 | return []string{"chaincode_proposal_payload"} 50 | } 51 | 52 | func (cap *ChaincodeActionPayload) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 53 | if name != cap.StaticallyOpaqueFields()[0] { 54 | return nil, fmt.Errorf("not a marshaled field: %s", name) 55 | } 56 | return &peer.ChaincodeProposalPayload{}, nil 57 | } 58 | 59 | type ChaincodeEndorsedAction struct { 60 | *peer.ChaincodeEndorsedAction 61 | } 62 | 63 | func (cae *ChaincodeEndorsedAction) Underlying() proto.Message { 64 | return cae.ChaincodeEndorsedAction 65 | } 66 | 67 | func (cae *ChaincodeEndorsedAction) StaticallyOpaqueFields() []string { 68 | return []string{"proposal_response_payload"} 69 | } 70 | 71 | func (cae *ChaincodeEndorsedAction) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 72 | if name != cae.StaticallyOpaqueFields()[0] { 73 | return nil, fmt.Errorf("not a marshaled field: %s", name) 74 | } 75 | return &peer.ProposalResponsePayload{}, nil 76 | } 77 | -------------------------------------------------------------------------------- /protolator/statically_opaque.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "reflect" 21 | 22 | "google.golang.org/protobuf/proto" 23 | ) 24 | 25 | func opaqueFrom(opaqueType func() (proto.Message, error), value interface{}, destType reflect.Type) (reflect.Value, error) { 26 | tree := value.(map[string]interface{}) // Safe, already checked 27 | nMsg, err := opaqueType() 28 | if err != nil { 29 | return reflect.Value{}, err 30 | } 31 | if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil { 32 | return reflect.Value{}, err 33 | } 34 | mMsg, err := MostlyDeterministicMarshal(nMsg) 35 | if err != nil { 36 | return reflect.Value{}, err 37 | } 38 | return reflect.ValueOf(mMsg), nil 39 | } 40 | 41 | func opaqueTo(opaqueType func() (proto.Message, error), value reflect.Value) (interface{}, error) { 42 | nMsg, err := opaqueType() 43 | if err != nil { 44 | return nil, err 45 | } 46 | mMsg := value.Interface().([]byte) // Safe, already checked 47 | if err = proto.Unmarshal(mMsg, nMsg); err != nil { 48 | return nil, err 49 | } 50 | return recursivelyCreateTreeFromMessage(nMsg) 51 | } 52 | 53 | type staticallyOpaqueFieldFactory struct{} 54 | 55 | func (soff staticallyOpaqueFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 56 | opaqueProto, ok := msg.(StaticallyOpaqueFieldProto) 57 | if !ok { 58 | return false 59 | } 60 | 61 | return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueFields()) 62 | } 63 | 64 | func (soff staticallyOpaqueFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 65 | opaqueProto := msg.(StaticallyOpaqueFieldProto) // Type checked in Handles 66 | 67 | return &plainField{ 68 | baseField: baseField{ 69 | msg: msg, 70 | name: fieldName, 71 | fType: mapStringInterfaceType, 72 | vType: bytesType, 73 | value: fieldValue, 74 | }, 75 | populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) { 76 | return opaqueFrom(func() (proto.Message, error) { return opaqueProto.StaticallyOpaqueFieldProto(fieldName) }, v, dT) 77 | }, 78 | populateTo: func(v reflect.Value) (interface{}, error) { 79 | return opaqueTo(func() (proto.Message, error) { return opaqueProto.StaticallyOpaqueFieldProto(fieldName) }, v) 80 | }, 81 | }, nil 82 | } 83 | 84 | type staticallyOpaqueMapFieldFactory struct{} 85 | 86 | func (soff staticallyOpaqueMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 87 | opaqueProto, ok := msg.(StaticallyOpaqueMapFieldProto) 88 | if !ok { 89 | return false 90 | } 91 | 92 | return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueMapFields()) 93 | } 94 | 95 | func (soff staticallyOpaqueMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 96 | opaqueProto := msg.(StaticallyOpaqueMapFieldProto) // Type checked in Handles 97 | 98 | return &mapField{ 99 | baseField: baseField{ 100 | msg: msg, 101 | name: fieldName, 102 | fType: mapStringInterfaceType, 103 | vType: fieldType, 104 | value: fieldValue, 105 | }, 106 | populateFrom: func(key string, v interface{}, dT reflect.Type) (reflect.Value, error) { 107 | return opaqueFrom(func() (proto.Message, error) { 108 | return opaqueProto.StaticallyOpaqueMapFieldProto(fieldName, key) 109 | }, v, dT) 110 | }, 111 | populateTo: func(key string, v reflect.Value) (interface{}, error) { 112 | return opaqueTo(func() (proto.Message, error) { 113 | return opaqueProto.StaticallyOpaqueMapFieldProto(fieldName, key) 114 | }, v) 115 | }, 116 | }, nil 117 | } 118 | 119 | type staticallyOpaqueSliceFieldFactory struct{} 120 | 121 | func (soff staticallyOpaqueSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 122 | opaqueProto, ok := msg.(StaticallyOpaqueSliceFieldProto) 123 | if !ok { 124 | return false 125 | } 126 | 127 | return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueSliceFields()) 128 | } 129 | 130 | func (soff staticallyOpaqueSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 131 | opaqueProto := msg.(StaticallyOpaqueSliceFieldProto) // Type checked in Handles 132 | 133 | return &sliceField{ 134 | baseField: baseField{ 135 | msg: msg, 136 | name: fieldName, 137 | fType: mapStringInterfaceType, 138 | vType: fieldType, 139 | value: fieldValue, 140 | }, 141 | populateFrom: func(index int, v interface{}, dT reflect.Type) (reflect.Value, error) { 142 | return opaqueFrom(func() (proto.Message, error) { 143 | return opaqueProto.StaticallyOpaqueSliceFieldProto(fieldName, index) 144 | }, v, dT) 145 | }, 146 | populateTo: func(index int, v reflect.Value) (interface{}, error) { 147 | return opaqueTo(func() (proto.Message, error) { 148 | return opaqueProto.StaticallyOpaqueSliceFieldProto(fieldName, index) 149 | }, v) 150 | }, 151 | }, nil 152 | } 153 | -------------------------------------------------------------------------------- /protolator/statically_opaque_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protolator 8 | 9 | import ( 10 | "bytes" 11 | "testing" 12 | 13 | "github.com/hyperledger/fabric-config/protolator/testprotos" 14 | "google.golang.org/protobuf/proto" 15 | 16 | . "github.com/onsi/gomega" 17 | ) 18 | 19 | func extractSimpleMsgPlainField(source []byte) string { 20 | result := &testprotos.SimpleMsg{} 21 | err := proto.Unmarshal(source, result) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return result.PlainField 26 | } 27 | 28 | func TestPlainStaticallyOpaqueMsg(t *testing.T) { 29 | gt := NewGomegaWithT(t) 30 | 31 | fromPrefix := "from" 32 | toPrefix := "to" 33 | tppff := &testProtoPlainFieldFactory{ 34 | fromPrefix: fromPrefix, 35 | toPrefix: toPrefix, 36 | } 37 | 38 | fieldFactories = []protoFieldFactory{tppff} 39 | 40 | pfValue := "foo" 41 | startMsg := &testprotos.StaticallyOpaqueMsg{ 42 | PlainOpaqueField: protoMarshalOrPanic(&testprotos.SimpleMsg{ 43 | PlainField: pfValue, 44 | }), 45 | } 46 | 47 | var buffer bytes.Buffer 48 | err := DeepMarshalJSON(&buffer, startMsg) 49 | gt.Expect(err).NotTo(HaveOccurred()) 50 | newMsg := &testprotos.StaticallyOpaqueMsg{} 51 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 52 | gt.Expect(err).NotTo(HaveOccurred()) 53 | gt.Expect(extractSimpleMsgPlainField(newMsg.PlainOpaqueField)).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.PlainOpaqueField))) 54 | 55 | fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueFieldFactory{}} 56 | 57 | buffer.Reset() 58 | err = DeepMarshalJSON(&buffer, startMsg) 59 | gt.Expect(err).NotTo(HaveOccurred()) 60 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 61 | gt.Expect(err).NotTo(HaveOccurred()) 62 | gt.Expect(extractSimpleMsgPlainField(newMsg.PlainOpaqueField)).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.PlainOpaqueField))) 63 | } 64 | 65 | func TestMapStaticallyOpaqueMsg(t *testing.T) { 66 | gt := NewGomegaWithT(t) 67 | 68 | fromPrefix := "from" 69 | toPrefix := "to" 70 | tppff := &testProtoPlainFieldFactory{ 71 | fromPrefix: fromPrefix, 72 | toPrefix: toPrefix, 73 | } 74 | 75 | fieldFactories = []protoFieldFactory{tppff} 76 | 77 | pfValue := "foo" 78 | mapKey := "bar" 79 | startMsg := &testprotos.StaticallyOpaqueMsg{ 80 | MapOpaqueField: map[string][]byte{ 81 | mapKey: protoMarshalOrPanic(&testprotos.SimpleMsg{ 82 | PlainField: pfValue, 83 | }), 84 | }, 85 | } 86 | 87 | var buffer bytes.Buffer 88 | err := DeepMarshalJSON(&buffer, startMsg) 89 | gt.Expect(err).NotTo(HaveOccurred()) 90 | newMsg := &testprotos.StaticallyOpaqueMsg{} 91 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 92 | gt.Expect(err).NotTo(HaveOccurred()) 93 | gt.Expect(extractSimpleMsgPlainField(newMsg.MapOpaqueField[mapKey])).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.MapOpaqueField[mapKey]))) 94 | 95 | fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueMapFieldFactory{}} 96 | 97 | buffer.Reset() 98 | err = DeepMarshalJSON(&buffer, startMsg) 99 | gt.Expect(err).NotTo(HaveOccurred()) 100 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 101 | gt.Expect(err).NotTo(HaveOccurred()) 102 | gt.Expect(extractSimpleMsgPlainField(newMsg.MapOpaqueField[mapKey])).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.MapOpaqueField[mapKey]))) 103 | } 104 | 105 | func TestSliceStaticallyOpaqueMsg(t *testing.T) { 106 | gt := NewGomegaWithT(t) 107 | 108 | fromPrefix := "from" 109 | toPrefix := "to" 110 | tppff := &testProtoPlainFieldFactory{ 111 | fromPrefix: fromPrefix, 112 | toPrefix: toPrefix, 113 | } 114 | 115 | fieldFactories = []protoFieldFactory{tppff} 116 | 117 | pfValue := "foo" 118 | startMsg := &testprotos.StaticallyOpaqueMsg{ 119 | SliceOpaqueField: [][]byte{ 120 | protoMarshalOrPanic(&testprotos.SimpleMsg{ 121 | PlainField: pfValue, 122 | }), 123 | }, 124 | } 125 | 126 | var buffer bytes.Buffer 127 | err := DeepMarshalJSON(&buffer, startMsg) 128 | gt.Expect(err).NotTo(HaveOccurred()) 129 | newMsg := &testprotos.StaticallyOpaqueMsg{} 130 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 131 | gt.Expect(err).NotTo(HaveOccurred()) 132 | gt.Expect(extractSimpleMsgPlainField(newMsg.SliceOpaqueField[0])).NotTo(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.SliceOpaqueField[0]))) 133 | 134 | fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueSliceFieldFactory{}} 135 | 136 | buffer.Reset() 137 | err = DeepMarshalJSON(&buffer, startMsg) 138 | gt.Expect(err).NotTo(HaveOccurred()) 139 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 140 | gt.Expect(err).NotTo(HaveOccurred()) 141 | gt.Expect(extractSimpleMsgPlainField(newMsg.SliceOpaqueField[0])).To(Equal(fromPrefix + toPrefix + extractSimpleMsgPlainField(startMsg.SliceOpaqueField[0]))) 142 | } 143 | 144 | func TestIgnoredNilFields(t *testing.T) { 145 | gt := NewGomegaWithT(t) 146 | 147 | _ = StaticallyOpaqueFieldProto(&testprotos.UnmarshalableDeepFields{}) 148 | _ = StaticallyOpaqueMapFieldProto(&testprotos.UnmarshalableDeepFields{}) 149 | _ = StaticallyOpaqueSliceFieldProto(&testprotos.UnmarshalableDeepFields{}) 150 | 151 | fieldFactories = []protoFieldFactory{ 152 | staticallyOpaqueFieldFactory{}, 153 | staticallyOpaqueMapFieldFactory{}, 154 | staticallyOpaqueSliceFieldFactory{}, 155 | } 156 | 157 | err := DeepMarshalJSON(&bytes.Buffer{}, &testprotos.UnmarshalableDeepFields{ 158 | PlainOpaqueField: []byte("fake"), 159 | }) 160 | gt.Expect(err).To(MatchError("*testprotos.UnmarshalableDeepFields: error in PopulateTo for field plain_opaque_field for message *testprotos.UnmarshalableDeepFields: intentional error")) 161 | err = DeepMarshalJSON(&bytes.Buffer{}, &testprotos.UnmarshalableDeepFields{ 162 | MapOpaqueField: map[string][]byte{"foo": []byte("bar")}, 163 | }) 164 | gt.Expect(err).To(MatchError("*testprotos.UnmarshalableDeepFields: error in PopulateTo for map field map_opaque_field and key foo for message *testprotos.UnmarshalableDeepFields: intentional error")) 165 | err = DeepMarshalJSON(&bytes.Buffer{}, &testprotos.UnmarshalableDeepFields{ 166 | SliceOpaqueField: [][]byte{[]byte("bar")}, 167 | }) 168 | gt.Expect(err).To(MatchError("*testprotos.UnmarshalableDeepFields: error in PopulateTo for slice field slice_opaque_field at index 0 for message *testprotos.UnmarshalableDeepFields: intentional error")) 169 | err = DeepMarshalJSON(&bytes.Buffer{}, &testprotos.UnmarshalableDeepFields{}) 170 | gt.Expect(err).NotTo(HaveOccurred()) 171 | } 172 | 173 | // protoMarshalOrPanic serializes a protobuf message and panics if this 174 | // operation fails 175 | func protoMarshalOrPanic(pb proto.Message) []byte { 176 | data, err := proto.Marshal(pb) 177 | if err != nil { 178 | panic(err) 179 | } 180 | 181 | return data 182 | } 183 | -------------------------------------------------------------------------------- /protolator/testprotos/sample.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testprotos 18 | 19 | import ( 20 | "fmt" 21 | 22 | "google.golang.org/protobuf/proto" 23 | ) 24 | 25 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueFields() []string { 26 | return []string{"plain_opaque_field"} 27 | } 28 | 29 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 30 | if name != som.StaticallyOpaqueFields()[0] { 31 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 32 | } 33 | 34 | return &SimpleMsg{}, nil 35 | } 36 | 37 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueMapFields() []string { 38 | return []string{"map_opaque_field"} 39 | } 40 | 41 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueMapFieldProto(name string, key string) (proto.Message, error) { 42 | if name != som.StaticallyOpaqueMapFields()[0] { 43 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 44 | } 45 | 46 | return &SimpleMsg{}, nil 47 | } 48 | 49 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueSliceFields() []string { 50 | return []string{"slice_opaque_field"} 51 | } 52 | 53 | func (som *StaticallyOpaqueMsg) StaticallyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) { 54 | if name != som.StaticallyOpaqueSliceFields()[0] { 55 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 56 | } 57 | 58 | return &SimpleMsg{}, nil 59 | } 60 | 61 | func typeSwitch(typeName string) (proto.Message, error) { 62 | switch typeName { 63 | case "SimpleMsg": 64 | return &SimpleMsg{}, nil 65 | case "NestedMsg": 66 | return &NestedMsg{}, nil 67 | case "StaticallyOpaqueMsg": 68 | return &StaticallyOpaqueMsg{}, nil 69 | case "VariablyOpaqueMsg": 70 | return &VariablyOpaqueMsg{}, nil 71 | default: 72 | return nil, fmt.Errorf("unknown message type: %s", typeName) 73 | } 74 | } 75 | 76 | func (vom *VariablyOpaqueMsg) VariablyOpaqueFields() []string { 77 | return []string{"plain_opaque_field"} 78 | } 79 | 80 | func (vom *VariablyOpaqueMsg) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 81 | if name != vom.VariablyOpaqueFields()[0] { 82 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 83 | } 84 | 85 | return typeSwitch(vom.OpaqueType) 86 | } 87 | 88 | func (vom *VariablyOpaqueMsg) VariablyOpaqueMapFields() []string { 89 | return []string{"map_opaque_field"} 90 | } 91 | 92 | func (vom *VariablyOpaqueMsg) VariablyOpaqueMapFieldProto(name string, key string) (proto.Message, error) { 93 | if name != vom.VariablyOpaqueMapFields()[0] { 94 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 95 | } 96 | 97 | return typeSwitch(vom.OpaqueType) 98 | } 99 | 100 | func (vom *VariablyOpaqueMsg) VariablyOpaqueSliceFields() []string { 101 | return []string{"slice_opaque_field"} 102 | } 103 | 104 | func (vom *VariablyOpaqueMsg) VariablyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) { 105 | if name != vom.VariablyOpaqueSliceFields()[0] { 106 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 107 | } 108 | 109 | return typeSwitch(vom.OpaqueType) 110 | } 111 | 112 | func (cm *ContextlessMsg) VariablyOpaqueFields() []string { 113 | return []string{"opaque_field"} 114 | } 115 | 116 | type DynamicMessageWrapper struct { 117 | *ContextlessMsg 118 | typeName string 119 | } 120 | 121 | func (dmw *DynamicMessageWrapper) VariablyOpaqueFieldProto(name string) (proto.Message, error) { 122 | if name != dmw.ContextlessMsg.VariablyOpaqueFields()[0] { 123 | return nil, fmt.Errorf("not a statically opaque field: %s", name) 124 | } 125 | 126 | return typeSwitch(dmw.typeName) 127 | } 128 | 129 | func (dmw *DynamicMessageWrapper) Underlying() proto.Message { 130 | return dmw.ContextlessMsg 131 | } 132 | 133 | func wrapContextless(underlying proto.Message, typeName string) (*DynamicMessageWrapper, error) { 134 | cm, ok := underlying.(*ContextlessMsg) 135 | if !ok { 136 | return nil, fmt.Errorf("unknown dynamic message to wrap (%T) requires *ContextlessMsg", underlying) 137 | } 138 | 139 | return &DynamicMessageWrapper{ 140 | ContextlessMsg: cm, 141 | typeName: typeName, 142 | }, nil 143 | } 144 | 145 | func (vom *DynamicMsg) DynamicFields() []string { 146 | return []string{"plain_dynamic_field"} 147 | } 148 | 149 | func (vom *DynamicMsg) DynamicFieldProto(name string, underlying proto.Message) (proto.Message, error) { 150 | if name != vom.DynamicFields()[0] { 151 | return nil, fmt.Errorf("not a dynamic field: %s", name) 152 | } 153 | 154 | return wrapContextless(underlying, vom.DynamicType) 155 | } 156 | 157 | func (vom *DynamicMsg) DynamicMapFields() []string { 158 | return []string{"map_dynamic_field"} 159 | } 160 | 161 | func (vom *DynamicMsg) DynamicMapFieldProto(name string, key string, underlying proto.Message) (proto.Message, error) { 162 | if name != vom.DynamicMapFields()[0] { 163 | return nil, fmt.Errorf("not a dynamic map field: %s", name) 164 | } 165 | 166 | return wrapContextless(underlying, vom.DynamicType) 167 | } 168 | 169 | func (vom *DynamicMsg) DynamicSliceFields() []string { 170 | return []string{"slice_dynamic_field"} 171 | } 172 | 173 | func (vom *DynamicMsg) DynamicSliceFieldProto(name string, index int, underlying proto.Message) (proto.Message, error) { 174 | if name != vom.DynamicSliceFields()[0] { 175 | return nil, fmt.Errorf("not a dynamic slice field: %s", name) 176 | } 177 | 178 | return wrapContextless(underlying, vom.DynamicType) 179 | } 180 | 181 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueFields() []string { 182 | return []string{"plain_opaque_field"} 183 | } 184 | 185 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueFieldProto(name string) (proto.Message, error) { 186 | return nil, fmt.Errorf("intentional error") 187 | } 188 | 189 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueMapFields() []string { 190 | return []string{"map_opaque_field"} 191 | } 192 | 193 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueMapFieldProto(name, key string) (proto.Message, error) { 194 | return nil, fmt.Errorf("intentional error") 195 | } 196 | 197 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueSliceFields() []string { 198 | return []string{"slice_opaque_field"} 199 | } 200 | 201 | func (udf *UnmarshalableDeepFields) StaticallyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) { 202 | return nil, fmt.Errorf("intentional error") 203 | } 204 | -------------------------------------------------------------------------------- /protolator/testprotos/sample.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | option go_package = "github.com/hyperledger/fabric-config/protolator/testprotos"; 20 | 21 | package testprotos; 22 | 23 | // SimpleMsg is designed to test that all three types of message fields, plain, map, 24 | // and slice are handled by the protolator tool 25 | message SimpleMsg { 26 | string plain_field = 1; 27 | map map_field = 2; 28 | repeated string slice_field = 3; 29 | } 30 | 31 | // NestedMsg is designed to test the nested message component 32 | message NestedMsg { 33 | SimpleMsg plain_nested_field = 1; 34 | map map_nested_field = 2; 35 | repeated SimpleMsg slice_nested_field = 3; 36 | } 37 | 38 | // StaticallyOpaqueMsg is designed to test the statically opaque message component 39 | // All fields are statically marshaled to the NestedMsg type 40 | message StaticallyOpaqueMsg { 41 | bytes plain_opaque_field = 1; 42 | map map_opaque_field = 2; 43 | repeated bytes slice_opaque_field = 3; 44 | } 45 | 46 | // VariablyOpaqueMsg is designed to test the staticaly opaque message component 47 | // The opaque type is determined by opaque_type 48 | message VariablyOpaqueMsg { 49 | string opaque_type = 1; 50 | bytes plain_opaque_field = 2; 51 | map map_opaque_field = 3; 52 | repeated bytes slice_opaque_field = 4; 53 | } 54 | 55 | // DynamicMsg is designed to test the dynamic message component 56 | // The dynamic wrapper applied to ContextlessMsg is determined by 57 | // dynamic_type 58 | message DynamicMsg { 59 | string dynamic_type = 1; 60 | ContextlessMsg plain_dynamic_field = 2; 61 | map map_dynamic_field = 3; 62 | repeated ContextlessMsg slice_dynamic_field = 4; 63 | } 64 | 65 | // ContextlessMsg is designed to carry a message of completely arbitrary type 66 | // Because there is no context for the type embedded in the message, the opaque 67 | // type must be dynamically added at runtime 68 | message ContextlessMsg { 69 | bytes opaque_field = 1; 70 | } 71 | 72 | // UnmarshalableDeepFields contains fields which are defined to be opaque, but will 73 | // return an error if they are asked to be deserialized. 74 | message UnmarshalableDeepFields { 75 | bytes plain_opaque_field = 1; 76 | map map_opaque_field = 2; 77 | repeated bytes slice_opaque_field = 3; 78 | } 79 | -------------------------------------------------------------------------------- /protolator/variably_opaque.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package protolator 18 | 19 | import ( 20 | "reflect" 21 | 22 | "google.golang.org/protobuf/proto" 23 | ) 24 | 25 | type variablyOpaqueFieldFactory struct{} 26 | 27 | func (soff variablyOpaqueFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 28 | opaqueProto, ok := msg.(VariablyOpaqueFieldProto) 29 | if !ok { 30 | return false 31 | } 32 | 33 | return stringInSlice(fieldName, opaqueProto.VariablyOpaqueFields()) 34 | } 35 | 36 | func (soff variablyOpaqueFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 37 | opaqueProto := msg.(VariablyOpaqueFieldProto) // Type checked in Handles 38 | 39 | return &plainField{ 40 | baseField: baseField{ 41 | msg: msg, 42 | name: fieldName, 43 | fType: mapStringInterfaceType, 44 | vType: bytesType, 45 | value: fieldValue, 46 | }, 47 | populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) { 48 | return opaqueFrom(func() (proto.Message, error) { return opaqueProto.VariablyOpaqueFieldProto(fieldName) }, v, dT) 49 | }, 50 | populateTo: func(v reflect.Value) (interface{}, error) { 51 | return opaqueTo(func() (proto.Message, error) { return opaqueProto.VariablyOpaqueFieldProto(fieldName) }, v) 52 | }, 53 | }, nil 54 | } 55 | 56 | type variablyOpaqueMapFieldFactory struct{} 57 | 58 | func (soff variablyOpaqueMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 59 | opaqueProto, ok := msg.(VariablyOpaqueMapFieldProto) 60 | if !ok { 61 | return false 62 | } 63 | 64 | return stringInSlice(fieldName, opaqueProto.VariablyOpaqueMapFields()) 65 | } 66 | 67 | func (soff variablyOpaqueMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 68 | opaqueProto := msg.(VariablyOpaqueMapFieldProto) // Type checked in Handles 69 | 70 | return &mapField{ 71 | baseField: baseField{ 72 | msg: msg, 73 | name: fieldName, 74 | fType: mapStringInterfaceType, 75 | vType: fieldType, 76 | value: fieldValue, 77 | }, 78 | populateFrom: func(key string, v interface{}, dT reflect.Type) (reflect.Value, error) { 79 | return opaqueFrom(func() (proto.Message, error) { 80 | return opaqueProto.VariablyOpaqueMapFieldProto(fieldName, key) 81 | }, v, dT) 82 | }, 83 | populateTo: func(key string, v reflect.Value) (interface{}, error) { 84 | return opaqueTo(func() (proto.Message, error) { 85 | return opaqueProto.VariablyOpaqueMapFieldProto(fieldName, key) 86 | }, v) 87 | }, 88 | }, nil 89 | } 90 | 91 | type variablyOpaqueSliceFieldFactory struct{} 92 | 93 | func (soff variablyOpaqueSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { 94 | opaqueProto, ok := msg.(VariablyOpaqueSliceFieldProto) 95 | if !ok { 96 | return false 97 | } 98 | 99 | return stringInSlice(fieldName, opaqueProto.VariablyOpaqueSliceFields()) 100 | } 101 | 102 | func (soff variablyOpaqueSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { 103 | opaqueProto := msg.(VariablyOpaqueSliceFieldProto) // Type checked in Handles 104 | 105 | return &sliceField{ 106 | baseField: baseField{ 107 | msg: msg, 108 | name: fieldName, 109 | fType: mapStringInterfaceType, 110 | vType: fieldType, 111 | value: fieldValue, 112 | }, 113 | populateFrom: func(index int, v interface{}, dT reflect.Type) (reflect.Value, error) { 114 | return opaqueFrom(func() (proto.Message, error) { 115 | return opaqueProto.VariablyOpaqueSliceFieldProto(fieldName, index) 116 | }, v, dT) 117 | }, 118 | populateTo: func(index int, v reflect.Value) (interface{}, error) { 119 | return opaqueTo(func() (proto.Message, error) { 120 | return opaqueProto.VariablyOpaqueSliceFieldProto(fieldName, index) 121 | }, v) 122 | }, 123 | }, nil 124 | } 125 | -------------------------------------------------------------------------------- /protolator/variably_opaque_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2017 All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package protolator 8 | 9 | import ( 10 | "bytes" 11 | "testing" 12 | 13 | "github.com/hyperledger/fabric-config/protolator/testprotos" 14 | "google.golang.org/protobuf/proto" 15 | 16 | . "github.com/onsi/gomega" 17 | ) 18 | 19 | func extractNestedMsgPlainField(source []byte) string { 20 | result := &testprotos.NestedMsg{} 21 | err := proto.Unmarshal(source, result) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return result.PlainNestedField.PlainField 26 | } 27 | 28 | func TestPlainVariablyOpaqueMsg(t *testing.T) { 29 | gt := NewGomegaWithT(t) 30 | 31 | fromPrefix := "from" 32 | toPrefix := "to" 33 | tppff := &testProtoPlainFieldFactory{ 34 | fromPrefix: fromPrefix, 35 | toPrefix: toPrefix, 36 | } 37 | 38 | fieldFactories = []protoFieldFactory{tppff} 39 | 40 | pfValue := "foo" 41 | startMsg := &testprotos.VariablyOpaqueMsg{ 42 | OpaqueType: "NestedMsg", 43 | PlainOpaqueField: protoMarshalOrPanic(&testprotos.NestedMsg{ 44 | PlainNestedField: &testprotos.SimpleMsg{ 45 | PlainField: pfValue, 46 | }, 47 | }), 48 | } 49 | 50 | var buffer bytes.Buffer 51 | err := DeepMarshalJSON(&buffer, startMsg) 52 | gt.Expect(err).NotTo(HaveOccurred()) 53 | newMsg := &testprotos.VariablyOpaqueMsg{} 54 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 55 | gt.Expect(err).NotTo(HaveOccurred()) 56 | gt.Expect(extractNestedMsgPlainField(newMsg.PlainOpaqueField)).NotTo(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.PlainOpaqueField))) 57 | 58 | fieldFactories = []protoFieldFactory{tppff, nestedFieldFactory{}, variablyOpaqueFieldFactory{}} 59 | 60 | buffer.Reset() 61 | err = DeepMarshalJSON(&buffer, startMsg) 62 | gt.Expect(err).NotTo(HaveOccurred()) 63 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 64 | gt.Expect(err).NotTo(HaveOccurred()) 65 | gt.Expect(extractNestedMsgPlainField(newMsg.PlainOpaqueField)).To(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.PlainOpaqueField))) 66 | } 67 | 68 | func TestMapVariablyOpaqueMsg(t *testing.T) { 69 | gt := NewGomegaWithT(t) 70 | 71 | fromPrefix := "from" 72 | toPrefix := "to" 73 | tppff := &testProtoPlainFieldFactory{ 74 | fromPrefix: fromPrefix, 75 | toPrefix: toPrefix, 76 | } 77 | 78 | fieldFactories = []protoFieldFactory{tppff} 79 | 80 | pfValue := "foo" 81 | mapKey := "bar" 82 | startMsg := &testprotos.VariablyOpaqueMsg{ 83 | OpaqueType: "NestedMsg", 84 | MapOpaqueField: map[string][]byte{ 85 | mapKey: protoMarshalOrPanic(&testprotos.NestedMsg{ 86 | PlainNestedField: &testprotos.SimpleMsg{ 87 | PlainField: pfValue, 88 | }, 89 | }), 90 | }, 91 | } 92 | 93 | var buffer bytes.Buffer 94 | err := DeepMarshalJSON(&buffer, startMsg) 95 | gt.Expect(err).NotTo(HaveOccurred()) 96 | newMsg := &testprotos.VariablyOpaqueMsg{} 97 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 98 | gt.Expect(err).NotTo(HaveOccurred()) 99 | gt.Expect(extractNestedMsgPlainField(newMsg.MapOpaqueField[mapKey])).NotTo(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.MapOpaqueField[mapKey]))) 100 | 101 | fieldFactories = []protoFieldFactory{tppff, nestedFieldFactory{}, variablyOpaqueMapFieldFactory{}} 102 | 103 | buffer.Reset() 104 | err = DeepMarshalJSON(&buffer, startMsg) 105 | gt.Expect(err).NotTo(HaveOccurred()) 106 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 107 | gt.Expect(err).NotTo(HaveOccurred()) 108 | gt.Expect(extractNestedMsgPlainField(newMsg.MapOpaqueField[mapKey])).To(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.MapOpaqueField[mapKey]))) 109 | } 110 | 111 | func TestSliceVariablyOpaqueMsg(t *testing.T) { 112 | gt := NewGomegaWithT(t) 113 | 114 | fromPrefix := "from" 115 | toPrefix := "to" 116 | tppff := &testProtoPlainFieldFactory{ 117 | fromPrefix: fromPrefix, 118 | toPrefix: toPrefix, 119 | } 120 | 121 | fieldFactories = []protoFieldFactory{tppff} 122 | 123 | pfValue := "foo" 124 | startMsg := &testprotos.VariablyOpaqueMsg{ 125 | OpaqueType: "NestedMsg", 126 | SliceOpaqueField: [][]byte{ 127 | protoMarshalOrPanic(&testprotos.NestedMsg{ 128 | PlainNestedField: &testprotos.SimpleMsg{ 129 | PlainField: pfValue, 130 | }, 131 | }), 132 | }, 133 | } 134 | 135 | var buffer bytes.Buffer 136 | err := DeepMarshalJSON(&buffer, startMsg) 137 | gt.Expect(err).NotTo(HaveOccurred()) 138 | newMsg := &testprotos.VariablyOpaqueMsg{} 139 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 140 | gt.Expect(err).NotTo(HaveOccurred()) 141 | gt.Expect(extractNestedMsgPlainField(newMsg.SliceOpaqueField[0])).NotTo(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.SliceOpaqueField[0]))) 142 | 143 | fieldFactories = []protoFieldFactory{tppff, nestedFieldFactory{}, variablyOpaqueSliceFieldFactory{}} 144 | 145 | buffer.Reset() 146 | err = DeepMarshalJSON(&buffer, startMsg) 147 | gt.Expect(err).NotTo(HaveOccurred()) 148 | err = DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg) 149 | gt.Expect(err).NotTo(HaveOccurred()) 150 | gt.Expect(extractNestedMsgPlainField(newMsg.SliceOpaqueField[0])).To(Equal(fromPrefix + toPrefix + extractNestedMsgPlainField(startMsg.SliceOpaqueField[0]))) 151 | } 152 | --------------------------------------------------------------------------------