├── .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 | 
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 | 
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 | [](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 |
--------------------------------------------------------------------------------