├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── api ├── ccpackager.go ├── chaincode.go ├── chaincode_test.go ├── channel.go ├── channel_test.go ├── common.go ├── config_test.go ├── discover.go ├── event.go ├── event_test.go ├── ledger.go ├── ledger_test.go ├── network.go ├── network_test.go ├── participant.go ├── peer.go └── peer_test.go ├── build.sh ├── docs └── images │ ├── chaincodeinvoke.png │ ├── chaincodelist.png │ ├── ledgerquery.png │ └── peerlist.png ├── go.mod ├── go.sum ├── log └── log.go ├── main └── main.go ├── service ├── chaincode.go ├── channel.go ├── common.go ├── event.go ├── ledger.go ├── network.go ├── network_test.go ├── peer.go ├── root.go ├── session.go └── websocket.go ├── test ├── chaincodes │ ├── example02java │ │ ├── build.gradle │ │ ├── settings.gradle │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── hyperledger │ │ │ └── fabric │ │ │ └── example │ │ │ └── SimpleChaincode.java │ ├── example02node │ │ └── src │ │ │ ├── chaincode_example02.js │ │ │ └── package.json │ ├── example02node_src.tar │ ├── vehiclesharing │ │ └── src │ │ │ └── fablet │ │ │ └── vs │ │ │ ├── META-INF │ │ │ └── statedb │ │ │ │ └── couchdb │ │ │ │ └── indexes │ │ │ │ ├── indexBrand.json │ │ │ │ └── indexId.json │ │ │ ├── collections_config.json │ │ │ └── vehiclesharing.go │ ├── vs_src.tar │ └── vs_src.tar.gz └── connprofiles │ ├── conn_profile_pre.yaml │ ├── conn_profile_simple.yaml │ ├── conn_profile_simple_12.yaml │ └── conn_profile_simple_2.yaml ├── util ├── network.go └── util.go └── web ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── fablet.xcf ├── fablet192.png ├── fablet512.png ├── fablet64.png ├── favicon.ico ├── index.html └── manifest.json └── src ├── common ├── constants.js ├── debugflag.js ├── env.js ├── log.js └── utils.js ├── components ├── chaincode │ ├── event.js │ ├── execute.js │ ├── install.js │ └── instantiate.js ├── channel │ ├── create.js │ └── join.js ├── ledger │ ├── blockchart.js │ └── detail.js ├── main.js ├── menu │ ├── connection.js │ └── list_items.js └── network │ ├── discover.js │ ├── peer_overview.js │ └── profile.js ├── css └── common.css ├── data └── localstore.js ├── i18n ├── cn.js ├── en.js └── index.js ├── index.css ├── index.js ├── serviceWorker.js └── util └── wstest.js /.gitignore: -------------------------------------------------------------------------------- 1 | web/node_modules/ 2 | web/yarn.lock 3 | release/ 4 | devtest/ 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${cwd}/main", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testFlags": [ 3 | "-v", 4 | "-count=1" 5 | ], 6 | "go.autocompleteUnimportedPackages": true, 7 | "go.docsTool": "gogetdoc", 8 | "go.formatTool": "goimports", 9 | "go.testTimeout": "3600s", 10 | "files.exclude": { 11 | "web/": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 1. 2019-03-18 4 | The first pre realase v0.0.1 was realease. 5 | And this project information is broadcasted to others. 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at stevemar@ca.ibm.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing In General 2 | Our project welcomes external contributions. If you have an itch, please feel 3 | free to scratch it. 4 | 5 | To contribute code or documentation, please submit a [pull request](https://github.com/IBM/fablet/pulls). 6 | 7 | A good way to familiarize yourself with the codebase and contribution process is 8 | to look for and tackle low-hanging fruit in the [issue tracker](https://github.com/IBM/fablet/issues). 9 | Before embarking on a more ambitious contribution, please quickly [get in touch](#communication) with us. 10 | 11 | **Note: We appreciate your effort, and want to avoid a situation where a contribution 12 | requires extensive rework (by you or by us), sits in backlog for a long time, or 13 | cannot be accepted at all!** 14 | 15 | ### Proposing new features 16 | 17 | If you would like to implement a new feature, please [raise an issue](https://github.com/IBM/fablet/issues) 18 | before sending a pull request so the feature can be discussed. This is to avoid 19 | you wasting your valuable time working on a feature that the project developers 20 | are not interested in accepting into the code base. 21 | 22 | ### Fixing bugs 23 | 24 | If you would like to fix a bug, please [raise an issue](https://github.com/IBM/fablet/issues) before sending a 25 | pull request so it can be tracked. 26 | 27 | ### Merge approval 28 | 29 | The project maintainers use LGTM (Looks Good To Me) in comments on the code 30 | review to indicate acceptance. A change requires LGTMs from two of the 31 | maintainers of each component affected. 32 | 33 | For a list of the maintainers, see the [MAINTAINERS.md](MAINTAINERS.md) page. 34 | 35 | ## Legal 36 | 37 | Each source file must include a license header for the Apache 38 | Software License 2.0. Using the SPDX format is the simplest approach. 39 | e.g. 40 | 41 | ``` 42 | /* 43 | Copyright All Rights Reserved. 44 | 45 | SPDX-License-Identifier: Apache-2.0 46 | */ 47 | ``` 48 | 49 | We simply ask that when submitting a patch for review, the developer 50 | must include a sign-off statement in the commit message. 51 | 52 | Here is an example Signed-off-by line, which indicates that the 53 | submitter accepts the DCO: 54 | 55 | ``` 56 | Signed-off-by: John Doe 57 | ``` 58 | 59 | You can include this automatically when you commit a change to your 60 | local git repository using the following command: 61 | 62 | ``` 63 | git commit -s 64 | ``` 65 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # MAINTAINERS 2 | 3 | Chun Lei (Tom) Xu - xchunlei@cn.ibm.com 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fablet 2 | Fablet, a browser-based dashboard and tools set for Hyperledger Fabric blockchain platform. 3 | It can help blockchain participants to connect to Fabric network, and perform operations of chaincode, channel, ledger... 4 | 5 | ## Overview 6 | 7 | ### Peers list 8 | ![Peers list](docs/images/peerlist.png) 9 | 10 | ### Chaincodes list 11 | ![Chaincodes list](docs/images/chaincodelist.png) 12 | 13 | ### Invoke chaincode 14 | ![Invoke chaincode](docs/images/chaincodeinvoke.png) 15 | 16 | ### Query ledger 17 | ![Query ledger](docs/images/ledgerquery.png) 18 | 19 | # Playground 20 | An example Fablet service was set up with a Fabric network, you can try it at: 21 | https://bctest01.fablet.pub:8081/ 22 | *Due to the self-sign certificate, please accept the security exception in your browser.* 23 | 24 | ## Initial connection 25 | 26 | If it is the first time you access the service, you need to create a connection profile and identity, we provide some examples corresponding to an example Fabric network (Fabric 1.4.3 first-network). Please download these accordinginly: 27 | 28 | * Connection Profile 29 | https://bctest01.fablet.pub:8081/test/conn_profile_simple.yaml 30 | 31 | * Identity certificate: 32 | https://bctest01.fablet.pub:8081/test/Admin@org1.example.com-cert.pem 33 | 34 | * Identity private key: 35 | https://bctest01.fablet.pub:8081/test/Admin@org1.example.com.key 36 | 37 | * MSP ID 38 | The corresponding MSP ID is `Org1MSP` 39 | 40 | ## Chaincode 41 | 42 | ### Installation 43 | *An exmaple chaincode `vsinst` has already been instantiated.* 44 | *And you can also install another chaincode instance with below packages.* 45 | *You can download the example package file and look into it for details.* 46 | 47 | * Tar file 48 | https://bctest01.fablet.pub:8081/test/vs_src.tar 49 | 50 | * Tar.gz file (same with above but was compressed) 51 | https://bctest01.fablet.pub:8081/test/vs_src.tar.gz 52 | 53 | * Chaincode path 54 | The corresponding chaincode path is `fablet/vs`. 55 | 56 | * Chaincode name and version 57 | Please input name and version. Example is `vehiclesharing` and `1.0`. 58 | 59 | * *Another example Node chaincode package for Fablet. (Example02 from first-network) 60 | https://bctest01.fablet.pub:8081/test/example02node_src.tar* 61 | 62 | ### Instantiation 63 | *This is a lower machine, it might take several minutes...* 64 | * Policy 65 | ``` 66 | OR ('Org1MSP.peer','Org2MSP.peer') 67 | ``` 68 | 69 | * Constructor parameters 70 | For `vehiclesharing` example chaincode, please leave it as blank. 71 | 72 | ### Execution 73 | * Function name 74 | ``` 75 | createVehicle 76 | ``` 77 | 78 | * Arguments 79 | ``` 80 | v001,brand001 81 | ``` 82 | 83 | ### Query 84 | * Function name 85 | ``` 86 | findVehicle 87 | ``` 88 | 89 | * Arguments 90 | ``` 91 | v001 92 | ``` 93 | 94 | *No more document for user now. I think that user should get all points from the UI directly, instead of documentation.* 95 | 96 | # Binaries 97 | 98 | Fablet executable binary (based on Ubuntu 18.04) was built and can be downloaded from here: https://github.com/IBM/fablet/releases/download/v0.0.1/fablet_0.0.1.tar.gz 99 | 100 | ## Start 101 | 102 | The build output will be found at ./release//fablet. 103 | 104 | * Start Fablet as default with http on port 8080: 105 | ``` 106 | ./release//fablet 107 | ``` 108 | 109 | * Start Fablet with https on customized port 8081, and TCP address: 110 | ``` 111 | ./release//fablet -addr localhost -port 8081 -cert -key 112 | ``` 113 | 114 | When Fablet start, you can access it via browser (We tested it on Chrome and Firefox). For connection profile and identity encryption materials, please see section of 'Playground' for examples. 115 | 116 | 117 | # Build 118 | 119 | *Now, building and development was only validated on Ubuntu 18.04.* 120 | 121 | ## Prerequisite 122 | 123 | * Go ^1.13.4 124 | If there is network issues, please try: 125 | `go env -w GOPROXY=https://goproxy.io,direct` 126 | 127 | * Node.js ^12.13.0 128 | 129 | * yarn ^1.19.1 130 | `npm install -g yarn` 131 | 132 | * Hyperledger Fabric 1.4.3 133 | Currently, Fablet supports Fabric 1.4.3, we are working to adapt to 2.0.0. Please refer to Fabric installation document for details. 134 | 135 | *You can use Fablet to connect to an existing Fabric blockchain network.* 136 | 137 | ## Download repository 138 | 139 | ``` 140 | git clone https://github.com/IBM/fablet.git 141 | ``` 142 | 143 | ## Build 144 | 145 | * Build all 146 | ``` 147 | ./build.sh 148 | ``` 149 | 150 | * Build service (go) project only 151 | ``` 152 | ./build.sh service 153 | ``` 154 | 155 | * Build web (js/react) project only 156 | ``` 157 | ./build web 158 | ``` 159 | 160 | ## Start 161 | 162 | Please find above section of 'Start' for details. 163 | 164 | # Development 165 | 166 | It is composed of 2 projects: service project and web project. These 2 projects folders can be opened as individual project, by MS Code or other IDE. 167 | 168 | ## Service project 169 | Under folder `./`. 170 | It is developed in Go language, it provides web service, and html/js/image host. 171 | * Run in development 172 | ``` 173 | go run ./main 174 | ``` 175 | * Test 176 | There are some testing programs in this project, before running those, you have to update connection profiles under folder `./test/connprofiles`. 177 | Basically the profiles are suitable for the `fabric-samples/first-network` example Fabric network. Please update the `tlsCACerts` section in the connection profiles with the certificates, an example is `fabric-samples/first-network/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem`. 178 | 179 | If you want to use your self Fabric blockchain network for testing purposes, please update `tlsCACerts` with the certificates accordinginly. 180 | 181 | These connection profiles can also be used to create connection to the Fabric blockchain network, via the Fablet web UI. 182 | 183 | ## Web project 184 | Under folder `./web`. 185 | It is developed in Javascript with React. 186 | 187 | * Debug environment 188 | Please open `./web/src/common/debugflag.js`, and then change const variable `DEBUG` to `true` in development. 189 | 190 | * Dependencies 191 | ``` 192 | yarn install 193 | ``` 194 | 195 | * Run in development 196 | ``` 197 | yarn start 198 | ``` 199 | Then open browser and access the default site: http://localhost:3000. 200 | -------------------------------------------------------------------------------- /api/ccpackager.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright SecureKey Technologies Inc. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package api 8 | 9 | import ( 10 | "archive/tar" 11 | "bytes" 12 | "compress/gzip" 13 | "io" 14 | "os" 15 | "path/filepath" 16 | "time" 17 | 18 | "github.com/pkg/errors" 19 | 20 | "fmt" 21 | 22 | pb "github.com/hyperledger/fabric-protos-go/peer" 23 | "github.com/hyperledger/fabric-sdk-go/pkg/fab/resource" 24 | ) 25 | 26 | // Functions copied from packager.go of fabric-sdk-go, to support different Node chaincode package. 27 | 28 | // Descriptor ... 29 | type Descriptor struct { 30 | name string 31 | fqp string 32 | } 33 | 34 | // NewNodeCCPackage creates new go lang chaincode package 35 | func NewJavaCCPackage(chaincodeRealPath string) (*resource.CCPackage, error) { 36 | descriptors, err := findSource(chaincodeRealPath) 37 | if err != nil { 38 | return nil, err 39 | } 40 | tarBytes, err := generateTarGz(descriptors) 41 | if err != nil { 42 | return nil, err 43 | } 44 | ccPkg := &resource.CCPackage{Type: pb.ChaincodeSpec_JAVA, Code: tarBytes} 45 | return ccPkg, nil 46 | } 47 | 48 | // NewNodeCCPackage creates new go lang chaincode package 49 | func NewNodeCCPackage(chaincodeRealPath string) (*resource.CCPackage, error) { 50 | descriptors, err := findSource(chaincodeRealPath) 51 | if err != nil { 52 | return nil, err 53 | } 54 | tarBytes, err := generateTarGz(descriptors) 55 | if err != nil { 56 | return nil, err 57 | } 58 | ccPkg := &resource.CCPackage{Type: pb.ChaincodeSpec_NODE, Code: tarBytes} 59 | return ccPkg, nil 60 | } 61 | 62 | func findSource(filePath string) ([]*Descriptor, error) { 63 | var descriptors []*Descriptor 64 | err := filepath.Walk(filePath, 65 | func(path string, fileInfo os.FileInfo, err error) error { 66 | if err != nil { 67 | return err 68 | } 69 | if fileInfo.Mode().IsRegular() && isSource(path) { 70 | relPath, err := filepath.Rel(filePath, path) 71 | if err != nil { 72 | return err 73 | } 74 | descriptors = append(descriptors, &Descriptor{name: relPath, fqp: path}) 75 | } 76 | return nil 77 | }) 78 | 79 | return descriptors, err 80 | } 81 | 82 | func isSource(filePath string) bool { 83 | return true 84 | } 85 | 86 | func generateTarGz(descriptors []*Descriptor) ([]byte, error) { 87 | var codePackage bytes.Buffer 88 | gw := gzip.NewWriter(&codePackage) 89 | tw := tar.NewWriter(gw) 90 | for _, v := range descriptors { 91 | err := packEntry(tw, gw, v) 92 | if err != nil { 93 | err1 := closeStream(tw, gw) 94 | if err1 != nil { 95 | return nil, errors.Wrap(err, fmt.Sprintf("packEntry failed and close error %s", err1)) 96 | } 97 | return nil, errors.Wrap(err, "packEntry failed") 98 | } 99 | } 100 | err := closeStream(tw, gw) 101 | if err != nil { 102 | return nil, errors.Wrap(err, "closeStream failed") 103 | } 104 | return codePackage.Bytes(), nil 105 | 106 | } 107 | 108 | func closeStream(tw io.Closer, gw io.Closer) error { 109 | err := tw.Close() 110 | if err != nil { 111 | return err 112 | } 113 | err = gw.Close() 114 | return err 115 | } 116 | 117 | func packEntry(tw *tar.Writer, gw *gzip.Writer, descriptor *Descriptor) error { 118 | file, err := os.Open(descriptor.fqp) 119 | if err != nil { 120 | return err 121 | } 122 | defer func() { 123 | err := file.Close() 124 | if err != nil { 125 | logger.Errorf("error file close %s", err) 126 | } 127 | }() 128 | 129 | if stat, err := file.Stat(); err == nil { 130 | // now lets create the header as needed for this file within the tarball 131 | header := new(tar.Header) 132 | header.Name = descriptor.name 133 | header.Size = stat.Size() 134 | header.Mode = int64(stat.Mode()) 135 | // Use a deterministic "zero-time" for all date fields 136 | header.ModTime = time.Time{} 137 | header.AccessTime = time.Time{} 138 | header.ChangeTime = time.Time{} 139 | // write the header to the tarball archive 140 | if err := tw.WriteHeader(header); err != nil { 141 | return err 142 | } 143 | if _, err := io.Copy(tw, file); err != nil { 144 | return err 145 | } 146 | if err := tw.Flush(); err != nil { 147 | return err 148 | } 149 | if err := gw.Flush(); err != nil { 150 | return err 151 | } 152 | 153 | } 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /api/chaincode.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/hyperledger/fabric-protos-go/common" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" 10 | "github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry" 11 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 12 | "github.com/hyperledger/fabric-sdk-go/pkg/context" 13 | packager "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager" 14 | "github.com/hyperledger/fabric-sdk-go/pkg/fab/comm" 15 | "github.com/hyperledger/fabric-sdk-go/pkg/fab/resource" 16 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 17 | "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | // Chaincode chaincode 22 | type Chaincode struct { 23 | // For installation and instantiation 24 | Name string `json:"name"` 25 | Version string `json:"version"` 26 | Path string `json:"path"` // for go, it is package 27 | // For installtion only 28 | BasePath string `json:"basePath"` // for go, it is go path. /src/ will be the go chaincode real path. 29 | Type string `json:"type"` // see pb.ChaincodeSpec_Type 30 | Package []byte `json:"package"` 31 | // For instantiation only 32 | ChannelID string `json:"channelID"` // instantiated in channnel 33 | Policy string `json:"policy"` // Endorsement policy 34 | Constructor []string `json:"constructor"` // Arguments for instantiation 35 | // For status. The chaincode might be instantiated (on channel) by not installed (on peer). 36 | Installed bool `json:"installed"` // It might be false while channelID is not empty, since it is instantiated in channel but not installed in current peer. 37 | } 38 | 39 | const ( 40 | ChaincodeType_GOLANG = "golang" 41 | ChaincodeType_NODE = "node" 42 | ChaincodeType_JAVA = "java" 43 | ) 44 | 45 | func (cc *Chaincode) String() string { 46 | return fmt.Sprintf("%s:%s", cc.Name, cc.Version) 47 | } 48 | 49 | // InstallChaincode to handle the detailed corresponding message, and multiple peers - some failed issue, the installation will be executed for multiple times. 50 | // TODO to use resmgmt.InstallCC 51 | func InstallChaincode(conn *NetworkConnection, cc *Chaincode, targets []string) (map[string]ExecutionResult, error) { 52 | if len(targets) < 1 { 53 | return nil, errors.New("no any targets to install chaincode") 54 | } 55 | 56 | var ccPkg *resource.CCPackage 57 | var err error 58 | if cc.Type == ChaincodeType_GOLANG { 59 | ccPkg, err = packager.NewCCPackage(cc.Path, cc.BasePath) 60 | } else if cc.Type == ChaincodeType_NODE { 61 | ccPkg, err = NewNodeCCPackage(cc.Path) 62 | } else if cc.Type == ChaincodeType_JAVA { 63 | ccPkg, err = NewJavaCCPackage(cc.Path) 64 | } else { 65 | return nil, errors.Errorf("%s is not a valid chaincode type.", cc.Type) 66 | } 67 | 68 | if err != nil { 69 | return nil, errors.WithMessagef(err, "Error occurred when generating chaincode package of \"%s\"", cc.String()) 70 | } 71 | icr := resource.InstallChaincodeRequest{Name: cc.Name, Path: cc.Path, Version: cc.Version, Package: ccPkg} 72 | ctx := conn.Client 73 | reqCtx, cancel := context.NewRequest(ctx, context.WithTimeoutType(fab.PeerResponse)) 74 | defer cancel() 75 | 76 | var peers []fab.ProposalProcessor 77 | for _, target := range targets { 78 | peerCfg, err := comm.NetworkPeerConfig(ctx.EndpointConfig(), target) 79 | if err != nil { 80 | return nil, errors.WithMessagef(err, "Error occurred when finding target \"%s\"", target) 81 | } 82 | peer, err := ctx.InfraProvider().CreatePeerFromConfig(peerCfg) 83 | if err != nil { 84 | return nil, errors.WithMessagef(err, "Error occurred when getting network peer from target \"%s\"", target) 85 | } 86 | peers = append(peers, peer) 87 | } 88 | 89 | var errAll error = nil 90 | var results = make(map[string]ExecutionResult) 91 | var wg sync.WaitGroup 92 | 93 | // Run per peer, to get accurate result mapping. 94 | for _, peer := range peers { 95 | wg.Add(1) 96 | go func(processor fab.ProposalProcessor) { 97 | defer wg.Done() 98 | peerURL := processor.(fab.Peer).URL() 99 | logger.Info(fmt.Sprintf("Sending chaincode installation proposal request to %s", peerURL)) 100 | r, _, err := resource.InstallChaincode(reqCtx, icr, []fab.ProposalProcessor{processor}, resource.WithRetry(retry.DefaultResMgmtOpts)) 101 | if err != nil { 102 | errAll = errors.New("There is at least one chaincode installation got failed") 103 | results[peerURL] = ExecutionResult{ 104 | Code: ResultFailure, 105 | Message: errors.WithMessage(err, "Error occurred when installing chaincode").Error()} 106 | return 107 | } 108 | // Must be 1 response 109 | response := r[0] 110 | if response.Status != int32(common.Status_SUCCESS) { 111 | errAll = errors.New("There is at least one chaincode installation got failed") 112 | results[peerURL] = ExecutionResult{ 113 | Code: ResultFailure, 114 | Message: errors.WithMessagef(err, "Error occurred when installing chaincode, the original status is %d", response.Status).Error()} 115 | } else { 116 | results[peerURL] = ExecutionResult{Code: ResultSuccess} 117 | } 118 | }(peer) 119 | } 120 | 121 | wg.Wait() 122 | return results, errAll 123 | } 124 | 125 | // InstantiateChaincode to instantiate chaincode. 126 | func InstantiateChaincode(conn *NetworkConnection, cc *Chaincode, target string, orderer string) (fab.TransactionID, error) { 127 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 128 | if err != nil { 129 | return "", errors.WithMessagef(err, "Failed to create new resource management client.") 130 | } 131 | 132 | ccPolicy, err := cauthdsl.FromString(cc.Policy) 133 | if err != nil { 134 | return "", errors.WithMessagef(err, "Failed to sing the policy for instantiation.") 135 | } 136 | 137 | argBytes := [][]byte{} 138 | for _, arg := range cc.Constructor { 139 | argBytes = append(argBytes, []byte(arg)) 140 | } 141 | 142 | insRes, err := resMgmtClient.InstantiateCC( 143 | cc.ChannelID, 144 | resmgmt.InstantiateCCRequest{ 145 | Name: cc.Name, 146 | Path: cc.Path, 147 | Version: cc.Version, 148 | Args: argBytes, 149 | Policy: ccPolicy, 150 | // TODO CollConfig and some other options. 151 | //CollConfig: collConfigs, 152 | }, 153 | resmgmt.WithRetry(retry.DefaultResMgmtOpts), 154 | resmgmt.WithTargetEndpoints(target), 155 | resmgmt.WithOrdererEndpoint(orderer), 156 | ) 157 | 158 | if err != nil { 159 | return "", errors.WithMessagef(err, "Failed to instantiate the chaincode %s:%s on channel %s.", cc.Name, cc.Version, cc.ChannelID) 160 | } 161 | logger.Infof("Succeed instantiated the chaincode %s:%s on channel %s.", cc.Name, cc.Version, cc.ChannelID) 162 | 163 | return insRes.TransactionID, nil 164 | } 165 | 166 | // UpgradeChaincode to upgrade chaincode. 167 | func UpgradeChaincode(conn *NetworkConnection, cc *Chaincode, target string, orderer string) (fab.TransactionID, error) { 168 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 169 | if err != nil { 170 | return "", errors.WithMessagef(err, "Failed to create new resource management client.") 171 | } 172 | 173 | ccPolicy, err := cauthdsl.FromString(cc.Policy) 174 | if err != nil { 175 | return "", errors.WithMessagef(err, "Failed to sing the policy for instantiation.") 176 | } 177 | 178 | argBytes := [][]byte{} 179 | for _, arg := range cc.Constructor { 180 | argBytes = append(argBytes, []byte(arg)) 181 | } 182 | 183 | updRes, err := resMgmtClient.UpgradeCC( 184 | cc.ChannelID, 185 | resmgmt.UpgradeCCRequest{ 186 | Name: cc.Name, 187 | Path: cc.Path, 188 | Version: cc.Version, 189 | Args: argBytes, 190 | Policy: ccPolicy, 191 | // TODO CollConfig and some other options. 192 | //CollConfig: collConfigs, 193 | }, 194 | resmgmt.WithRetry(retry.DefaultResMgmtOpts), 195 | resmgmt.WithTargetEndpoints(target), 196 | resmgmt.WithOrdererEndpoint(orderer), 197 | ) 198 | 199 | if err != nil { 200 | return "", errors.WithMessagef(err, "Failed to upgrade the chaincode %s:%s on channel %s.", cc.Name, cc.Version, cc.ChannelID) 201 | } 202 | logger.Infof("Succeed upgrade the chaincode %s:%s on channel %s.", cc.Name, cc.Version, cc.ChannelID) 203 | 204 | return updRes.TransactionID, nil 205 | } 206 | 207 | // ChaincodeOperType chaincode operation type of 208 | type ChaincodeOperType int 209 | 210 | const ( 211 | // ChaincodeOperTypeExecute execute a chaincode 212 | ChaincodeOperTypeExecute ChaincodeOperType = iota 213 | // ChaincodeOperTypeQuery query a chaincode 214 | ChaincodeOperTypeQuery 215 | ) 216 | 217 | // ExecuteChaincode to invoke a chaincode 218 | // TODO to determine the targets 219 | func ExecuteChaincode(conn *NetworkConnection, channelID string, chaincodeID string, 220 | operType ChaincodeOperType, targets []string, 221 | funcName string, args []string, 222 | options ...channel.RequestOption) (*channel.Response, error) { 223 | channelContext := conn.SDK.ChannelContext(channelID, fabsdk.WithIdentity(conn.SignID)) 224 | channelClient, err := channel.New(channelContext) 225 | 226 | if err != nil { 227 | return nil, errors.WithMessagef(err, "Error occurred when creating a new client for channel %s.", channelID) 228 | } 229 | 230 | argsByte := make([][]byte, len(args)) 231 | for idx, arg := range args { 232 | argsByte[idx] = []byte(arg) 233 | } 234 | 235 | reqOpts := []channel.RequestOption{} 236 | reqOpts = append(reqOpts, channel.WithTargetEndpoints(targets...)) 237 | reqOpts = append(reqOpts, channel.WithRetry(retry.DefaultChannelOpts)) 238 | reqOpts = append(reqOpts, options...) 239 | 240 | oper := channelClient.Execute 241 | if operType == ChaincodeOperTypeQuery { 242 | oper = channelClient.Query 243 | } 244 | 245 | response, err := oper( 246 | channel.Request{ 247 | ChaincodeID: chaincodeID, 248 | Fcn: funcName, 249 | Args: argsByte, 250 | }, 251 | reqOpts..., 252 | ) 253 | 254 | if err != nil { 255 | return nil, errors.WithMessagef(err, "Error occurred when executing the chaincode %s.", chaincodeID) 256 | } 257 | 258 | return &response, nil 259 | } 260 | -------------------------------------------------------------------------------- /api/chaincode_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright SecureKey Technologies Inc. All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package api 8 | 9 | import ( 10 | "fmt" 11 | "math/rand" 12 | "testing" 13 | "time" 14 | 15 | uuid "github.com/satori/go.uuid" 16 | ) 17 | 18 | const ( 19 | chaincodeName = "vehiclesharing" 20 | chaincodePath = "fablet/vs" 21 | gopath = "../test/chaincodes/vehiclesharing" 22 | // Chaincode path after 'src', and the Chaincode path will be also used for installation. See below. 23 | // Gopath path before 'src' 24 | 25 | ) 26 | 27 | // Refined. 28 | 29 | func getRandomCCVersion() string { 30 | return uuid.NewV1().String()[0:8] 31 | } 32 | 33 | func getRandCC() *Chaincode { 34 | ccVersion := getRandomCCVersion() 35 | ccName := "vs" + ccVersion 36 | return &Chaincode{ 37 | Name: ccName, 38 | Version: ccVersion, 39 | Path: chaincodePath, 40 | BasePath: gopath, 41 | Type: ChaincodeType_GOLANG, 42 | } 43 | } 44 | 45 | func getRandNodeCC() *Chaincode { 46 | ccVersion := getRandomCCVersion() 47 | ccName := "ccnode" + ccVersion 48 | return &Chaincode{ 49 | Name: ccName, 50 | Version: ccVersion, 51 | Path: "../test/chaincodes/example02node", 52 | Type: ChaincodeType_NODE, 53 | } 54 | } 55 | 56 | func getRandJavaCC() *Chaincode { 57 | ccVersion := getRandomCCVersion() 58 | ccName := "ccjava" + ccVersion 59 | return &Chaincode{ 60 | Name: ccName, 61 | Version: ccVersion, 62 | Path: "../test/chaincodes/example02java", 63 | Type: ChaincodeType_JAVA, 64 | } 65 | } 66 | 67 | func TestInstallCCByAPI(t *testing.T) { 68 | cc := getRandCC() 69 | t.Log("Begin install chaincode: ", cc.Name, cc.Version) 70 | conn, err := getConnectionSimple() 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | res, err := InstallChaincode(conn, cc, []string{target01}) 75 | if err != nil { 76 | t.Log(err.Error()) 77 | } 78 | for k, m := range res { 79 | t.Log(k, m) 80 | } 81 | } 82 | 83 | func TestCCPackageNode(t *testing.T) { 84 | cc := getRandNodeCC() 85 | t.Log("Begin install chaincode: ", cc.Name, cc.Version) 86 | ccPkg, err := NewNodeCCPackage(cc.Path) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | t.Log(ccPkg.Type.String()) 91 | } 92 | 93 | func TestInstallAndInstantiateNodeCCByAPI(t *testing.T) { 94 | cc := getRandNodeCC() 95 | t.Log("Begin install chaincode: ", cc.Name, cc.Version) 96 | conn, err := getConnectionSimple() 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | defer conn.Close() 101 | res, err := InstallChaincode(conn, cc, []string{target11}) 102 | 103 | if err != nil { 104 | t.Log(err.Error()) 105 | } 106 | for k, m := range res { 107 | t.Log(k, m) 108 | } 109 | 110 | t.Log("Wait for 2 seconds.") 111 | time.Sleep(time.Second * 5) 112 | 113 | cc.Policy = "OR ('Org1MSP.peer','Org2MSP.peer')" 114 | cc.Constructor = []string{"init", "a", "100", "b", "200"} 115 | cc.ChannelID = mychannel 116 | 117 | tid, err := InstantiateChaincode(conn, cc, target11, orderer) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | t.Logf("Succeed instantiate chaincode %s.", string(tid)) 122 | } 123 | 124 | func TestInstallAndInstantiateJavaCCByAPI(t *testing.T) { 125 | cc := getRandJavaCC() 126 | t.Log("Begin install chaincode: ", cc.Name, cc.Version) 127 | conn, err := getConnectionSimple() 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | res, err := InstallChaincode(conn, cc, []string{target01}) 132 | if err != nil { 133 | t.Log(err.Error()) 134 | } 135 | for k, m := range res { 136 | t.Log(k, m) 137 | } 138 | 139 | t.Log("Wait for 2 seconds.") 140 | time.Sleep(time.Second * 2) 141 | 142 | cc.Policy = "OR ('Org1MSP.peer','Org2MSP.peer')" 143 | cc.Constructor = []string{"init", "a", "100", "b", "200"} 144 | cc.ChannelID = mychannel 145 | 146 | tid, err := InstantiateChaincode(conn, cc, target01, orderer) 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | t.Logf("Succeed instantiate chaincode %s.", string(tid)) 151 | } 152 | 153 | func TestInstallAndInstantiateCCByAPI(t *testing.T) { 154 | cc := getRandCC() 155 | t.Log("Begin install and instantiate chaincode: ", cc.Name, cc.Version) 156 | conn, err := getConnectionSimple() 157 | 158 | res, err := InstallChaincode(conn, cc, []string{target01}) 159 | if err != nil { 160 | t.Fatal(err.Error()) 161 | } 162 | for k, m := range res { 163 | t.Log(k, m) 164 | } 165 | 166 | t.Log("Wait for 2 seconds.") 167 | time.Sleep(time.Second * 2) 168 | 169 | cc.Policy = "OR ('Org1MSP.peer','Org2MSP.peer')" 170 | cc.Constructor = []string{} 171 | cc.ChannelID = mychannel 172 | 173 | tid, err := InstantiateChaincode(conn, cc, target01, orderer) 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | t.Logf("Succeed instantiate chaincode %s.", string(tid)) 178 | } 179 | 180 | func TestInstallAndInstantiateCCByAPIMultiple(t *testing.T) { 181 | for i := 0; i < 10; i++ { 182 | fmt.Printf("Installing %d\r\n", i) 183 | TestInstallAndInstantiateCCByAPI(t) 184 | } 185 | } 186 | 187 | func TestInstallAndUpgradeCCByAPI(t *testing.T) { 188 | ccVersion := getRandomCCVersion() 189 | ccName := "vehiclesharing" 190 | cc := &Chaincode{ 191 | Name: ccName, 192 | Version: ccVersion, 193 | Path: chaincodePath, 194 | BasePath: gopath, 195 | Type: ChaincodeType_GOLANG, 196 | } 197 | t.Log("Begin install and upgrade chaincode: ", cc.Name, cc.Version) 198 | conn, err := getConnectionSimple() 199 | 200 | res, err := InstallChaincode(conn, cc, []string{target01}) 201 | if err != nil { 202 | t.Fatal(err.Error()) 203 | } 204 | for k, m := range res { 205 | t.Log(k, m) 206 | } 207 | 208 | t.Log("Wait for 2 seconds.") 209 | time.Sleep(time.Second * 2) 210 | 211 | cc.Policy = "OR ('Org1MSP.peer','Org2MSP.peer')" 212 | cc.Constructor = []string{} 213 | cc.ChannelID = mychannel 214 | 215 | tid, err := UpgradeChaincode(conn, cc, target01, orderer) 216 | if err != nil { 217 | t.Fatal(err) 218 | } 219 | t.Logf("Succeed upgrade chaincode %s.", string(tid)) 220 | } 221 | 222 | func TestExecuteCCByAPI(t *testing.T) { 223 | t.Log("Begin execute chaincode") 224 | conn, err := getConnectionSimple() 225 | 226 | r := getRandomCCVersion() 227 | res, err := ExecuteChaincode(conn, mychannel, "vehiclesharing", ChaincodeOperTypeExecute, 228 | []string{target01}, "createVehicle", []string{"k_" + r, "b" + r}) 229 | if err != nil { 230 | t.Fatal(err) 231 | } 232 | t.Log("Transaction ID:", res.TransactionID) 233 | t.Log("TxValidationCode", res.TxValidationCode) 234 | t.Log("ChaincodeStatus", res.ChaincodeStatus) 235 | t.Log("Payload", string(res.Payload)) 236 | 237 | for _, rr := range res.Responses { 238 | t.Log("<<<<<<<<<<<<<<<<<<<<<", rr, ">>>>>>>>>>>>>>>>>>>>>") 239 | t.Log(rr.Endorser) 240 | t.Log(rr.GetVersion()) 241 | t.Log(string(rr.GetResponse().GetPayload())) 242 | t.Log(rr.GetResponse().GetStatus()) 243 | } 244 | } 245 | 246 | func TestExecuteCCRoutine(t *testing.T) { 247 | t.Log("Begin execute chaincode") 248 | conn, _ := getConnectionSimple() 249 | 250 | //var wg sync.WaitGroup 251 | 252 | for i := 0; i < 1; i++ { 253 | //wg.Add(1) 254 | go func(i int) { 255 | fmt.Printf("Executing %d\n", i) 256 | //defer wg.Done() 257 | for { 258 | //updateVehiclePrice 259 | time.Sleep(time.Second * time.Duration(rand.Int31n(5))) 260 | r := getRandomCCVersion() 261 | res, err := ExecuteChaincode(conn, mychannel, vehiclesharing, ChaincodeOperTypeExecute, 262 | []string{target01}, "createVehicle", []string{"k_" + r, "b" + r}) 263 | if err != nil { 264 | fmt.Println(err) 265 | } 266 | fmt.Println("Transaction ID: (createVehicle)", res.TransactionID) 267 | } 268 | }(i) 269 | } 270 | 271 | time.Sleep(time.Second * 3600) 272 | 273 | //wg.Wait() 274 | } 275 | 276 | func TestExecuteCCRoutineUpdate(t *testing.T) { 277 | t.Log("Begin execute chaincode") 278 | conn, _ := getConnectionSimple() 279 | 280 | //var wg sync.WaitGroup 281 | 282 | for i := 0; i < 1; i++ { 283 | //wg.Add(1) 284 | go func(i int) { 285 | fmt.Printf("Executing %d\n", i) 286 | //defer wg.Done() 287 | for { 288 | //updateVehiclePrice 289 | time.Sleep(time.Second * time.Duration(rand.Int31n(5))) 290 | res, err := ExecuteChaincode(conn, mychannel, vehiclesharing, ChaincodeOperTypeExecute, 291 | []string{target01}, "updateVehiclePrice", []string{"v001", "100"}) 292 | if err != nil { 293 | fmt.Println(err) 294 | } 295 | fmt.Println("Transaction ID: (updateVehiclePrice)", res.TransactionID) 296 | } 297 | }(i) 298 | } 299 | 300 | time.Sleep(time.Second * 3600) 301 | 302 | //wg.Wait() 303 | } 304 | 305 | func TestExecuteCCRoutine2Action(t *testing.T) { 306 | go TestExecuteCCRoutine(t) 307 | go TestExecuteCCRoutineUpdate(t) 308 | 309 | time.Sleep(time.Second * 3600) 310 | } 311 | -------------------------------------------------------------------------------- /api/channel.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "github.com/gogo/protobuf/proto" 6 | "github.com/hyperledger/fabric-protos-go/common" 7 | "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/fab/resource" 10 | ) 11 | 12 | // getJoinedChannels to get all joined channels of an endpoint. 13 | func getJoinedChannels(conn *NetworkConnection, endpointURL string) ([]string, error) { 14 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 15 | if err != nil { 16 | return nil, err 17 | } 18 | chresp, err := resMgmtClient.QueryChannels(resmgmt.WithTargetEndpoints(endpointURL)) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | channelIDs := []string{} 24 | for _, channelInfo := range chresp.Channels { 25 | channelIDs = append(channelIDs, channelInfo.GetChannelId()) 26 | } 27 | return channelIDs, nil 28 | } 29 | 30 | // CreateChannel to create a channel 31 | func CreateChannel(conn *NetworkConnection, txContent []byte, orderer string) (string, error) { 32 | cub, _ := resource.ExtractChannelConfig(txContent) 33 | cu := &common.ConfigUpdate{} 34 | err := proto.Unmarshal(cub, cu) 35 | if err != nil { 36 | return "", err 37 | } 38 | channelID := cu.GetChannelId() 39 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 40 | req := resmgmt.SaveChannelRequest{ 41 | ChannelID: channelID, 42 | ChannelConfig: bytes.NewReader(txContent)} 43 | 44 | _, err = resMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(orderer)) 45 | if err != nil { 46 | return "", err 47 | } 48 | return channelID, nil 49 | } 50 | 51 | // JoinChannel to join a peer into channel 52 | func JoinChannel(conn *NetworkConnection, channelID string, targets []string, orderer string) error { 53 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 54 | if err != nil { 55 | return err 56 | } 57 | return resMgmtClient.JoinChannel(channelID, 58 | resmgmt.WithRetry(retry.DefaultResMgmtOpts), 59 | resmgmt.WithOrdererEndpoint(orderer), 60 | resmgmt.WithTargetEndpoints(targets...)) 61 | } 62 | -------------------------------------------------------------------------------- /api/channel_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | // "github.com/hyperledger/fabric-sdk-go/pkg/client/event" 6 | //"github.com/hyperledger/fabric/protos/utils" 7 | //"github.com/hyperledger/fabric/protos/utils" 8 | //protosmsp "github.com/hyperledger/fabric/protos/msp" 9 | ) 10 | 11 | func TestCreateChannelByAPI(t *testing.T) { 12 | conn, err := getConnectionSimple() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | channelID, err := CreateChannel(conn, channelTx, orderer) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | t.Log("Create a new channel: ", channelID) 22 | } 23 | 24 | func TestJoinChannelByAPI(t *testing.T) { 25 | conn, err := getConnectionPre() 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | err = JoinChannel(conn, "chtest2", []string{target01}, orderer) 31 | if err != nil { 32 | t.Fatal(err) 33 | } else { 34 | t.Log("Adding channel got succeed") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/common.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/x509" 5 | "time" 6 | 7 | "github.com/IBM/fablet/log" 8 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 10 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 11 | 12 | "github.com/IBM/fablet/util" 13 | ) 14 | 15 | var logger = log.GetLogger() 16 | 17 | // DiscoverTimeOut timeout for discovery 18 | const DiscoverTimeOut = 30 * time.Second 19 | 20 | // ResultCode uint32 21 | type ResultCode uint32 22 | 23 | const ( 24 | // ResultSuccess success 25 | ResultSuccess ResultCode = 0 26 | // ResultFailure failure 27 | ResultFailure ResultCode = 1 28 | ) 29 | 30 | const ( 31 | // LSCC code of lifecycle chaincode 32 | LSCC = "lscc" 33 | ) 34 | 35 | // ExecutionResult execution result 36 | type ExecutionResult struct { 37 | Code ResultCode `json:"code"` 38 | Message string `json:"message"` 39 | Result interface{} `json:"result"` 40 | } 41 | 42 | // Peer common peer instance 43 | type Peer struct { 44 | Name string `json:"name"` 45 | OrgName string `json:"orgName"` 46 | MSPID string `json:"MSPID"` 47 | URL string `json:"URL"` // from PeerConfig 48 | TLSCACert *x509.Certificate `json:"TLSCACert"` // from PeerConfig 49 | GRPCOptions map[string]interface{} `json:"GRPCOptions"` // from PeerConfig 50 | Channels util.Set `json:"channels"` 51 | PeerChannelConfigs map[string]*fab.PeerChannelConfig `json:"peerChannelConfigs"` // map[channelID] 52 | IsConfigured bool `json:"isConfigured"` 53 | UpdateTime int64 `json:"updateTime"` 54 | // Chaincodes []*Chaincode `json:"chaincodes"` //TODO @deprecated 55 | } 56 | 57 | // AddChannel add channel ID 58 | // func (peer *Peer) AddChannel(channelID string) { 59 | // if !util.ExistString(peer.Channels, channelID) { 60 | // peer.Channels = append(peer.Channels, channelID) 61 | // } 62 | // } 63 | 64 | // Orderer common orderer instance 65 | type Orderer struct { 66 | Name string `json:"name"` 67 | URL string `json:"URL"` // from OrdererConfig 68 | GRPCOptions map[string]interface{} `json:"TLSCACert"` // from OrdererConfig 69 | TLSCACert *x509.Certificate `json:"GRPCOptions"` // from OrdererConfig 70 | Channels util.Set `json:"channels"` 71 | } 72 | 73 | // Organization org 74 | type Organization struct { 75 | Name string `json:"name"` 76 | MSPID string `json:"MSPID"` 77 | CryptoPath string `json:"cryptoPath"` 78 | Peers util.Set `json:"peers"` 79 | } 80 | 81 | // Channel channel 82 | type Channel struct { 83 | ChannelID string `json:"channelID"` 84 | Peers util.Set `json:"peers"` 85 | AnchorPeers util.Set `json:"anchorPeers"` 86 | Orderers util.Set `json:"orderers"` 87 | Policies *fab.ChannelPolicies `json:"policies"` 88 | } 89 | 90 | // NetworkConnection the entry to the Fabric network. 91 | // TODO to have lock for update, and a safe disconnection when update/close. 92 | type NetworkConnection struct { 93 | // Materials to initialize the connection 94 | *ConnectionProfile 95 | *Participant 96 | UseDiscovery bool 97 | 98 | // To identify the connection from others. Normally be hash of connection profile, participant and useDiscovery. 99 | Identifier string 100 | UpdateTime time.Time 101 | ActiveTime time.Time 102 | 103 | // Some intermediate/context based on the sdk. 104 | SDK *fabsdk.FabricSDK 105 | Client context.Client 106 | ClientProvider context.ClientProvider 107 | 108 | // Conifguration and discovered result, they are only for indication or presentation, not for the network directly. 109 | Channels map[string]*Channel 110 | Organizations map[string]*Organization 111 | Peers map[string]*Peer 112 | Orderers map[string]*Orderer 113 | 114 | EndpointStatuses map[string]util.EndPointStatus 115 | ChannelLedgers map[string]*Ledger 116 | ChannelChaincodes map[string][]*Chaincode 117 | ChannelOrderers map[string][]*Orderer 118 | ChannelAnchorPeers map[string][]string 119 | } 120 | 121 | // NetworkOverview for whole network 122 | type NetworkOverview struct { 123 | Peers []*Peer `json:"peers"` 124 | Channels []*Channel `json:"channels"` 125 | EndpointStatuses map[string]util.EndPointStatus `json:"endpointStatuses"` 126 | ChannelOrderers map[string][]*Orderer `json:"channelOrderers"` 127 | ChannelLedgers map[string]*Ledger `json:"channelLedgers"` 128 | ChannelChainCodes map[string][]*Chaincode `json:"channelChaincodes"` 129 | ChannelAnchorPeers map[string][]string `json:"channelAnchorPeers"` 130 | } 131 | 132 | // PeerStatus peer status, corresponding to EndpointStatus 133 | type PeerStatus struct { 134 | Ping bool `json:"ping"` 135 | GRPC bool `json:"GRPC"` 136 | Valid bool `json:"valid"` 137 | } 138 | -------------------------------------------------------------------------------- /api/config_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | ) 7 | 8 | // Please change this to your own fabric-samples folder. 9 | var fabricSamplePath = "/home/tom/fabric/fabric-samples" 10 | 11 | var yamlConfigType = "yaml" 12 | 13 | var connConfig, _ = ioutil.ReadFile("../test/connprofiles/conn_profile_simple.yaml") 14 | var connConfig12, _ = ioutil.ReadFile("../test/connprofiles/conn_profile_simple_12.yaml") 15 | var connConfigPre, _ = ioutil.ReadFile("../test/connprofiles/conn_profile_pre.yaml") 16 | 17 | var channelTx, _ = ioutil.ReadFile(filepath.Join(fabricSamplePath, "/first-network/channel-artifacts/chtest.tx")) 18 | 19 | var testCert, _ = ioutil.ReadFile(filepath.Join(fabricSamplePath, "/first-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem")) 20 | var testPrivKey, _ = ioutil.ReadFile(getOnlyFile(filepath.Join(fabricSamplePath, "/first-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore"))) 21 | 22 | var testCert2, _ = ioutil.ReadFile(filepath.Join(fabricSamplePath, "/first-network/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem")) 23 | var testPrivKey2, _ = ioutil.ReadFile(getOnlyFile(filepath.Join(fabricSamplePath, "/first-network/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore"))) 24 | 25 | var target01 = "peer0.org1.example.com:7051" 26 | var target11 = "peer1.org1.example.com:8051" 27 | var target02 = "peer0.org2.example.com:9051" 28 | var target12 = "peer0.org2.example.com:10051" 29 | 30 | var mspIDOrg1 = "Org1MSP" 31 | var mspIDOrg2 = "Org2MSP" 32 | 33 | var orderer = "orderer.example.com:7050" 34 | var orderer2 = "orderer2.example.com:8050" 35 | 36 | var mychannel = "mychannel" 37 | var vehiclesharing = "vehiclesharing" 38 | 39 | func getOnlyFile(path string) string { 40 | files, err := ioutil.ReadDir(path) 41 | if err != nil { 42 | panic("err read folder: " + path) 43 | } 44 | if len(files) < 1 { 45 | panic("No at least 1 file") 46 | } 47 | return filepath.Join(path, files[0].Name()) 48 | } 49 | 50 | func getConnectionSimple() (*NetworkConnection, error) { 51 | return NewConnection( 52 | &ConnectionProfile{connConfig, yamlConfigType}, 53 | &Participant{"TestAdmin", "", mspIDOrg1, testCert, testPrivKey, nil}, true) 54 | } 55 | 56 | func getConnectionPre() (*NetworkConnection, error) { 57 | return NewConnection( 58 | &ConnectionProfile{connConfigPre, yamlConfigType}, 59 | &Participant{"TestAdmin", "", mspIDOrg1, testCert, testPrivKey, nil}, true) 60 | } 61 | -------------------------------------------------------------------------------- /api/discover.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/hyperledger/fabric-protos-go/peer" 7 | 8 | "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/fab/comm" 10 | ) 11 | 12 | // DiscoverOption to collect all options in discovery 13 | type DiscoverOption struct { 14 | // List of endpoint urls, only discover/monitor these peers. 15 | Targets []string `json:"discoverPeers"` 16 | IsDetail bool `json:"isDetail"` 17 | } 18 | 19 | func (opt *DiscoverOption) isTarget(endpointURL string) bool { 20 | // Default empty allows all. 21 | if len(opt.Targets) == 0 { 22 | return true 23 | } 24 | for _, p := range opt.Targets { 25 | if p == endpointURL { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | // DiscoverOptionFunc to handle the discovery option 33 | type DiscoverOptionFunc func(opt *DiscoverOption) error 34 | 35 | // WithTargets only retrieve details of the peer. 36 | func WithTargets(targets ...string) DiscoverOptionFunc { 37 | return func(opt *DiscoverOption) error { 38 | opt.Targets = targets 39 | return nil 40 | } 41 | } 42 | 43 | // WithIsDetail only retrieve details of the peer. 44 | func WithIsDetail(isDetail bool) DiscoverOptionFunc { 45 | return func(opt *DiscoverOption) error { 46 | opt.IsDetail = isDetail 47 | return nil 48 | } 49 | } 50 | 51 | // DiscoverNetworkOverview To get all peers by discover. 52 | // TODO for now, only 1 endpoint config is well supported. 53 | // Fault tolerant. 54 | func DiscoverNetworkOverview(conn *NetworkConnection, options ...DiscoverOptionFunc) (*NetworkOverview, error) { 55 | // return allPeers, nil 56 | peers := []*Peer{} 57 | for _, peer := range conn.Peers { 58 | peers = append(peers, peer) 59 | } 60 | 61 | channels := []*Channel{} 62 | for _, channel := range conn.Channels { 63 | channels = append(channels, channel) 64 | } 65 | 66 | sort.SliceStable(peers, func(i, j int) bool { 67 | if peers[i].MSPID != peers[j].MSPID { 68 | return peers[i].MSPID < peers[j].MSPID 69 | } 70 | return peers[i].Name < peers[j].Name 71 | }) 72 | 73 | return &NetworkOverview{ 74 | Peers: peers, 75 | Channels: channels, 76 | EndpointStatuses: conn.EndpointStatuses, 77 | ChannelOrderers: conn.ChannelOrderers, 78 | ChannelLedgers: conn.ChannelLedgers, 79 | ChannelChainCodes: conn.ChannelChaincodes, 80 | ChannelAnchorPeers: conn.ChannelAnchorPeers, 81 | }, nil 82 | } 83 | 84 | func generateOption(options ...DiscoverOptionFunc) *DiscoverOption { 85 | disOpt := &DiscoverOption{} 86 | for _, opt := range options { 87 | opt(disOpt) 88 | } 89 | return disOpt 90 | } 91 | 92 | func existChaincode(ccs []*Chaincode, name string, version string) bool { 93 | for _, cc := range ccs { 94 | if cc.Name == name && cc.Version == version { 95 | return true 96 | } 97 | } 98 | return false 99 | } 100 | 101 | func existChaincodeInstalled(ccs []*peer.ChaincodeInfo, name string, version string) bool { 102 | for _, cc := range ccs { 103 | if cc.Name == name && cc.Version == version { 104 | return true 105 | } 106 | } 107 | return false 108 | } 109 | 110 | // GetJoinedChannels to get all joined channels of an endpoint. 111 | func GetJoinedChannels(conn *NetworkConnection, endpointURL string, options ...DiscoverOptionFunc) ([]*peer.ChannelInfo, error) { 112 | ctx := conn.Client 113 | 114 | peerCfg, err := comm.NetworkPeerConfig(ctx.EndpointConfig(), endpointURL) 115 | if err != nil { 116 | return nil, err 117 | } 118 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 119 | if err != nil { 120 | return nil, err 121 | } 122 | p, err := ctx.InfraProvider().CreatePeerFromConfig(peerCfg) 123 | if err != nil { 124 | return nil, err 125 | } 126 | // TODO to use resmgmt.WithTargetEndpoints to be more easier. 127 | chresp, err := resMgmtClient.QueryChannels(resmgmt.WithTargets(p)) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | logger.Infof("Get %d joined channels from: %s", len(chresp.Channels), endpointURL) 133 | 134 | return chresp.Channels, nil 135 | } 136 | -------------------------------------------------------------------------------- /api/event.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/hyperledger/fabric-sdk-go/pkg/client/event" 5 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 6 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 7 | ) 8 | 9 | // MonitorBlockEvent to monitor block event 10 | func MonitorBlockEvent(conn *NetworkConnection, channelID string, 11 | eventChan chan<- *fab.FilteredBlockEvent, closeChan <-chan int, eventCloseChan chan<- int) error { 12 | logger.Debugf("MonitorBlockEvent of %s begins.", channelID) 13 | 14 | channelContext := conn.SDK.ChannelContext(channelID, fabsdk.WithIdentity(conn.SignID)) 15 | eventClient, err := event.New(channelContext) 16 | if err != nil { 17 | eventCloseChan <- 0 18 | logger.Debugf("Creating event got failed: %s.", err.Error()) 19 | return err 20 | } 21 | 22 | reg, notifier, err := eventClient.RegisterFilteredBlockEvent() 23 | // TODO if the event service is closed, i.e., the peer is shut dow. 24 | if err != nil { 25 | eventCloseChan <- 0 26 | logger.Debugf("Registration event got failed: %s.", err.Error()) 27 | return err 28 | } 29 | 30 | // TODO To unregister the event under all scenarios. 31 | // Make sure that this goroutine runs under a background context, such as web service, otherwise 32 | // it might be discarded when the system process exists. 33 | defer func() { 34 | eventClient.Unregister(reg) 35 | eventCloseChan <- 0 36 | logger.Debugf("MonitorBlockEvent of %s event unregistered.", channelID) 37 | }() 38 | 39 | for { 40 | select { 41 | case event := <-notifier: 42 | eventChan <- event 43 | case <-closeChan: 44 | return nil 45 | } 46 | } 47 | } 48 | 49 | // MonitorChaincodeEvent to monitor chaincode event 50 | func MonitorChaincodeEvent(conn *NetworkConnection, channelID string, chaincodeID string, eventFilter string, 51 | eventChan chan<- *fab.CCEvent, closeChan <-chan int, eventCloseChan chan<- error) { 52 | logger.Debugf("MonitorChaincodeEvent of %s %s %s begins.", channelID, chaincodeID, eventFilter) 53 | 54 | channelContext := conn.SDK.ChannelContext(channelID, fabsdk.WithIdentity(conn.SignID)) 55 | eventClient, err := event.New(channelContext, event.WithBlockEvents()) 56 | if err != nil { 57 | logger.Debugf("Creating event got failed: %s.", err.Error()) 58 | eventCloseChan <- err 59 | return 60 | } 61 | 62 | reg, notifier, err := eventClient.RegisterChaincodeEvent(chaincodeID, eventFilter) 63 | // TODO if the event service is closed, i.e., the peer is shut dow. 64 | if err != nil { 65 | logger.Debugf("Registration event got failed: %s.", err.Error()) 66 | eventCloseChan <- err 67 | return 68 | } 69 | 70 | defer func() { 71 | eventClient.Unregister(reg) 72 | eventCloseChan <- nil 73 | logger.Debugf("MonitorChaincodeEvent of %s %s %s unregistered.", channelID, chaincodeID, eventFilter) 74 | }() 75 | 76 | for { 77 | select { 78 | case event := <-notifier: 79 | eventChan <- event 80 | case <-closeChan: 81 | return 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /api/event_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 10 | ) 11 | 12 | func TestBlockEvent(t *testing.T) { 13 | t.Log("Begin event test") 14 | conn, err := getConnectionSimple() 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | eventChan := make(chan *fab.FilteredBlockEvent, 1) 20 | closeChan := make(chan int, 1) 21 | eventCloseChan := make(chan int, 1) 22 | go MonitorBlockEvent(conn, mychannel, eventChan, closeChan, eventCloseChan) 23 | 24 | // Mock client termination. 25 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 26 | // Mock background goroutine of web service. 27 | ctxBg, cancelBg := context.WithTimeout(context.Background(), time.Second*50) 28 | 29 | go func() { 30 | defer func() { 31 | logger.Info("Event end") 32 | closeChan <- 0 33 | cancel() 34 | }() 35 | 36 | for { 37 | select { 38 | case event := <-eventChan: 39 | logger.Debugf("Get an event from %s.", event.SourceURL) 40 | if event == nil { 41 | // return errors.Errorf("Event is nil") 42 | } else { 43 | fmt.Println(event.FilteredBlock.GetNumber()) 44 | } 45 | case <-eventCloseChan: 46 | return 47 | case <-ctx.Done(): 48 | return 49 | } 50 | } 51 | }() 52 | 53 | <-ctxBg.Done() 54 | cancelBg() 55 | logger.Debugf("Background context ends.") 56 | } 57 | 58 | func TestChaincodeEvent(t *testing.T) { 59 | t.Log("Begin event test") 60 | conn, err := getConnectionSimple() 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | fmt.Println("Begin event...") 66 | 67 | eventChan := make(chan *fab.CCEvent, 1) 68 | closeChan := make(chan int, 1) 69 | eventCloseChan := make(chan error, 1) 70 | 71 | go MonitorChaincodeEvent(conn, mychannel, vehiclesharing, ".*(create|update).*", eventChan, closeChan, eventCloseChan) 72 | 73 | go func() { 74 | i := 0 75 | for { 76 | fmt.Println(i, " second ...") 77 | time.Sleep(time.Second) 78 | i++ 79 | } 80 | }() 81 | 82 | // Mock client termination. 83 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*300) 84 | 85 | defer func() { 86 | logger.Info("Event end") 87 | closeChan <- 0 88 | cancel() 89 | }() 90 | for { 91 | select { 92 | case event := <-eventChan: 93 | fmt.Println(event.BlockNumber, event.ChaincodeID, event.EventName, event.SourceURL, event.SourceURL, string(event.Payload)) 94 | case <-eventCloseChan: 95 | return 96 | case <-ctx.Done(): 97 | return 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /api/ledger_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestUtilLedgerQuery to test query ledger 8 | func TestLedgerQueryByAPI(t *testing.T) { 9 | conn, err := getConnectionSimple() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | /////////////////////////////////// 15 | 16 | ledger, err := QueryLedger(conn, mychannel, nil) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | t.Log(">>>>>>>>>>>>>> ", ledger) 21 | 22 | blocks, err := QueryBlock(conn, mychannel, nil, 10, 1) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | t.Log(">>>>>>>>>>>>>> ", blocks) 27 | } 28 | 29 | func TestBlockLSCCAPI(t *testing.T) { 30 | conn, err := getConnectionSimple() 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | defer conn.Close() 36 | /////////////////////////////////// 37 | 38 | blocks, err := QueryBlock(conn, "chtest1", []string{target01}, 0, 8) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | for idx, block := range blocks { 43 | t.Log(idx, block.Time) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /api/network_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | // Refined. 10 | 11 | func TestNetwork(t *testing.T) { 12 | conn, err := NewConnection( 13 | &ConnectionProfile{connConfig, yamlConfigType}, 14 | &Participant{"TestAdmin", "", mspIDOrg1, testCert, testPrivKey, nil}, 15 | true, 16 | ) 17 | if err != nil { 18 | t.Fatal(errors.WithStack(err).Error()) 19 | } 20 | defer conn.Close() 21 | t.Log(conn.Show()) 22 | 23 | } 24 | 25 | func TestQueryInstalledChaincodes(t *testing.T) { 26 | conn, err := NewConnection( 27 | &ConnectionProfile{connConfig, yamlConfigType}, 28 | &Participant{"TestAdmin", "", mspIDOrg1, testCert, testPrivKey, nil}, 29 | true, 30 | ) 31 | if err != nil { 32 | t.Fatal(errors.WithStack(err).Error()) 33 | } 34 | defer conn.Close() 35 | 36 | ccs, err := QueryInstalledChaincodes(conn, target01) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | for _, cc := range ccs { 41 | t.Log(cc) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/participant.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp" 5 | fabImpl "github.com/hyperledger/fabric-sdk-go/pkg/fab" 6 | "github.com/pkg/errors" 7 | 8 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite" 10 | "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite/bccsp/sw" 11 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 12 | mspImpl "github.com/hyperledger/fabric-sdk-go/pkg/msp" 13 | ) 14 | 15 | type Participant struct { 16 | Label string 17 | OrgName string 18 | MSPID string 19 | Cert []byte 20 | PrivateKey []byte 21 | SignID msp.SigningIdentity 22 | } 23 | 24 | // Find the org name definied in the connection profile. 25 | func findOrgNameByMSPID(mspID string, orgs map[string]fab.OrganizationConfig) string { 26 | for name, org := range orgs { 27 | if org.MSPID == mspID { 28 | return name 29 | } 30 | } 31 | return "" 32 | } 33 | 34 | // CreateSigningIdentity To create identity by orgname, cert and key. 35 | func CreateSigningIdentity(sdk *fabsdk.FabricSDK, participant *Participant) (msp.SigningIdentity, error) { 36 | // configBackend, err := confProvider() 37 | // Reuse the configure provider for sdk. 38 | configBackend, err := sdk.Config() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | // Use file store 44 | // userStore, err := msp.NewCertFileUserStore(identityConfig.CredentialStorePath()) 45 | userStore := mspImpl.NewMemoryUserStore() 46 | 47 | cryptSuiteConfig := cryptosuite.ConfigFromBackend(configBackend) 48 | cryptoSuite, err := sw.GetSuiteByConfig(cryptSuiteConfig) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | endpointConfig, err := fabImpl.ConfigFromBackend(configBackend) 54 | 55 | orgs := endpointConfig.NetworkConfig().Organizations 56 | 57 | if participant.OrgName == "" { 58 | participant.OrgName = findOrgNameByMSPID(participant.MSPID, orgs) 59 | if participant.OrgName == "" { 60 | return nil, errors.Errorf("MSPID %s not found in the connection profile.", participant.MSPID) 61 | } 62 | } 63 | 64 | mgr, err := mspImpl.NewIdentityManager(participant.OrgName, userStore, cryptoSuite, endpointConfig) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | id, err := mgr.CreateSigningIdentity(msp.WithCert(participant.Cert), msp.WithPrivateKey(participant.PrivateKey)) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return id, nil 75 | } 76 | -------------------------------------------------------------------------------- /api/peer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" 5 | ) 6 | 7 | // QueryInstalledChaincodes to get all installed chaincodes 8 | func QueryInstalledChaincodes(conn *NetworkConnection, endpointURL string, options ...DiscoverOptionFunc) ([]*Chaincode, error) { 9 | resMgmtClient, err := resmgmt.New(conn.ClientProvider) 10 | if err != nil { 11 | return nil, err 12 | } 13 | ccRes, err := resMgmtClient.QueryInstalledChaincodes(resmgmt.WithTargetEndpoints(endpointURL)) 14 | if err != nil { 15 | return nil, err 16 | } 17 | ccs := []*Chaincode{} 18 | for _, installedCC := range ccRes.GetChaincodes() { 19 | ccs = append(ccs, &Chaincode{ 20 | Name: installedCC.GetName(), 21 | Version: installedCC.GetVersion(), 22 | Path: installedCC.GetPath(), 23 | Installed: true}) 24 | } 25 | return ccs, nil 26 | } 27 | -------------------------------------------------------------------------------- /api/peer_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func TestPeer(t *testing.T) { 10 | conn, err := NewConnection( 11 | &ConnectionProfile{connConfig, yamlConfigType}, 12 | &Participant{"TestAdmin", "", mspIDOrg1, testCert, testPrivKey, nil}, 13 | true, 14 | ) 15 | if err != nil { 16 | t.Fatal(errors.WithStack(err).Error()) 17 | } 18 | defer conn.Close() 19 | //t.Log(conn.Show()) 20 | 21 | ccs, err := QueryInstalledChaincodes(conn, target01) 22 | if err == nil { 23 | for _, cc := range ccs { 24 | t.Log(cc) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # Environment variable 2 | ARCH="$(uname -m)" 3 | OS_NAME="$(uname -s)" 4 | FABLET_PKG="github.com/IBM/fablet/main" 5 | RELEASE_TARGET="./release/${OS_NAME}_${ARCH}" 6 | FABLET_BIN="fablet" 7 | GO_BUILD_CMD="go build" 8 | WEB_BUILD_CMD="yarn build" 9 | WEB_INSTALL_PKG_CMD="yarn install" 10 | WEB_SRC="./web" 11 | WEB_SRC_BUILD="${WEB_SRC}/build" 12 | WEB_TARGET="${RELEASE_TARGET}/web" 13 | SERVICE="service" 14 | WEB="web" 15 | 16 | # From the WEB_SRC dir 17 | DEBUG_FLAG_DIR="./src/common" 18 | DEBUG_FLAG_FILE="${DEBUG_FLAG_DIR}/debugflag.js" 19 | DEBUG_FLAG_FILE_BAK="${DEBUG_FLAG_DIR}/debugflag.js.bak" 20 | 21 | mkReleaseFolder() { 22 | mkdir -p "${RELEASE_TARGET}" 23 | } 24 | 25 | buildService() { 26 | echo "Compile Fablet binary files." 27 | mkReleaseFolder 28 | rm -f "${RELEASE_TARGET}/${FABLET_BIN}" 29 | ${GO_BUILD_CMD} -o "${RELEASE_TARGET}/${FABLET_BIN}" ${FABLET_PKG} 30 | } 31 | 32 | setDebugFlagFalse() { 33 | rm -f ${DEBUG_FLAG_FILE_BAK} 34 | mv ${DEBUG_FLAG_FILE} ${DEBUG_FLAG_FILE_BAK} 35 | echo "export const DEBUG = false;" > ${DEBUG_FLAG_FILE} 36 | } 37 | 38 | setDebugFlagBack() { 39 | if [[ -f "$DEBUG_FLAG_FILE_BAK" ]]; then 40 | rm -f ${DEBUG_FLAG_FILE} 41 | mv ${DEBUG_FLAG_FILE_BAK} ${DEBUG_FLAG_FILE} 42 | fi 43 | } 44 | 45 | buildWeb() { 46 | echo "Compile Fablet web files." 47 | mkReleaseFolder 48 | rm -rf "${WEB_TARGET}" 49 | pushd "${WEB_SRC}" \ 50 | && ${WEB_INSTALL_PKG_CMD} \ 51 | && setDebugFlagFalse \ 52 | && ${WEB_BUILD_CMD} \ 53 | && setDebugFlagBack \ 54 | && popd \ 55 | && mv "${WEB_SRC_BUILD}" "${WEB_TARGET}" 56 | 57 | } 58 | 59 | 60 | build1=$1 61 | build2=$2 62 | if [ "${build1}" == "${SERVICE}" ] || [ "${build2}" == "${SERVICE}" ]; then 63 | buildService 64 | if [ $? -ne 0 ]; then 65 | echo "Error!" 66 | exit 1 67 | fi 68 | fi 69 | if [ "${build1}" == "${WEB}" ] || [ "${build2}" == "${WEB}" ]; then 70 | buildWeb 71 | if [ $? -ne 0 ]; then 72 | echo "Error!" 73 | exit 1 74 | fi 75 | fi 76 | if [ -z "${build1}" ] && [ -z "${build2}" ]; then 77 | buildService 78 | buildWeb 79 | fi 80 | 81 | echo "Please run ${RELEASE_TARGET}/${FABLET_BIN}" to start Fablet. -------------------------------------------------------------------------------- /docs/images/chaincodeinvoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/docs/images/chaincodeinvoke.png -------------------------------------------------------------------------------- /docs/images/chaincodelist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/docs/images/chaincodelist.png -------------------------------------------------------------------------------- /docs/images/ledgerquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/docs/images/ledgerquery.png -------------------------------------------------------------------------------- /docs/images/peerlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/docs/images/peerlist.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/IBM/fablet 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Shopify/sarama v1.26.1 // indirect 7 | github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 8 | github.com/docker/go-metrics v0.0.1 // indirect 9 | github.com/fsouza/go-dockerclient v1.6.0 // indirect 10 | github.com/gogo/protobuf v1.2.1 11 | github.com/gorilla/websocket v1.4.1 12 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect 13 | github.com/hashicorp/go-version v1.2.0 // indirect 14 | github.com/hyperledger/fabric v1.4.4 15 | github.com/hyperledger/fabric-amcl v0.0.0-20190902191507-f66264322317 // indirect 16 | github.com/hyperledger/fabric-protos-go v0.0.0-20190821180310-6b6ac9042dfd 17 | //github.com/hyperledger/fabric-protos-go v0.0.0-20191114160927-6bee4929a99f 18 | github.com/hyperledger/fabric-sdk-go v1.0.0-beta1 19 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect 20 | github.com/pkg/errors v0.8.1 21 | github.com/satori/go.uuid v1.2.0 22 | github.com/sykesm/zap-logfmt v0.0.3 // indirect 23 | go.uber.org/zap v1.13.0 // indirect 24 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | ) 8 | 9 | // TODO To log to file. 10 | 11 | type FabletLogger struct { 12 | } 13 | 14 | var commonLogger *FabletLogger 15 | var once sync.Once 16 | 17 | func Init() { 18 | once.Do(func() { 19 | if commonLogger == nil { 20 | log.Println("Initialize log.") 21 | commonLogger = &FabletLogger{} 22 | } 23 | }) 24 | } 25 | 26 | func GetLogger() *FabletLogger { 27 | return commonLogger 28 | } 29 | 30 | func (logger *FabletLogger) Info(msg ...interface{}) { 31 | log.Println("[INF]", fmt.Sprint(msg...)) 32 | } 33 | 34 | func (logger *FabletLogger) Infof(format string, msg ...interface{}) { 35 | log.Println("[INF]", fmt.Sprintf(format, msg...)) 36 | } 37 | 38 | func (logger *FabletLogger) Debug(msg ...interface{}) { 39 | log.Println("[DBG]", fmt.Sprint(msg...)) 40 | } 41 | func (logger *FabletLogger) Debugf(format string, msg ...interface{}) { 42 | log.Println("[DBG]", fmt.Sprintf(format, msg...)) 43 | } 44 | 45 | func (logger *FabletLogger) Error(msg ...interface{}) { 46 | log.Println("[ERR]", fmt.Sprint(msg...)) 47 | } 48 | func (logger *FabletLogger) Errorf(format string, msg ...interface{}) { 49 | log.Println("[ERR]", fmt.Sprintf(format, msg...)) 50 | } 51 | 52 | func (logger *FabletLogger) Warn(msg ...interface{}) { 53 | log.Println("[WRN]", fmt.Sprint(msg...)) 54 | } 55 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "path/filepath" 10 | "syscall" 11 | 12 | "github.com/IBM/fablet/log" 13 | 14 | "github.com/IBM/fablet/service" 15 | ) 16 | 17 | // SrvPort The default http listening port 18 | const DefaultSrvPort = 8080 19 | 20 | var logger = log.GetLogger() 21 | 22 | // TODO 23 | // TODO to persist the connection in session 24 | func getHandlerMap() map[string]service.HTTPHandler { 25 | handlerMap := map[string]service.HTTPHandler{ 26 | // "/": service.HandleRoot, 27 | //"/network/connect": service.Post(service.HandleNetworkConnect), 28 | "/network/discover": service.Post(service.HandleNetworkDiscover), 29 | "/network/refresh": service.Post(service.HandleNetworkRefresh), 30 | "/peer/details": service.Post(service.HandlePeerDetails), 31 | "/chaincode/install": service.Post(service.HandleChaincodeInstall), 32 | "/chaincode/instantiate": service.Post(service.HandleChaincodeInstantiate), 33 | "/chaincode/upgrade": service.Post(service.HandleChaincodeUpgrade), 34 | "/chaincode/execute": service.Post(service.HandleChaincodeExecute), 35 | "/ledger/query": service.Post(service.HandleLedgerQuery), 36 | "/ledger/block": service.Post(service.HandleBlockQuery), 37 | "/ledger/blockany": service.Post(service.HandleBlockQueryAny), 38 | "/channel/create": service.Post(service.HandleCreateChannel), 39 | "/channel/join": service.Post(service.HandleJoinChannel), 40 | "/event/blockevent": service.WS(service.HandleBlockEvent), 41 | "/event/chaincodeevent": service.WS(service.HandleChaincodeEvent), 42 | } 43 | 44 | return handlerMap 45 | } 46 | 47 | func main() { 48 | s := make(chan os.Signal, 1) 49 | signal.Notify(s, os.Interrupt, syscall.SIGTERM) 50 | 51 | addr := flag.String("addr", "", "Listen on the TCP address (default all)") 52 | port := flag.Int("port", DefaultSrvPort, "Listen on port") 53 | cert := flag.String("cert", "", "TLS cert (default non-https)") 54 | key := flag.String("key", "", "TLS key (default non-https)") 55 | flag.Parse() 56 | 57 | addrPort := fmt.Sprintf("%s:%d", *addr, *port) 58 | 59 | for url, handler := range getHandlerMap() { 60 | http.HandleFunc(url, handler) 61 | } 62 | http.Handle("/", http.FileServer(http.Dir(filepath.Join(service.ExeFolder, "web")))) 63 | 64 | go func() { 65 | var err error 66 | if *cert != "" && *key != "" { 67 | err = http.ListenAndServeTLS(addrPort, *cert, *key, nil) 68 | } else { 69 | err = http.ListenAndServe(addrPort, nil) 70 | } 71 | if err != nil { 72 | logger.Error(err.Error()) 73 | s <- syscall.SIGTERM 74 | } 75 | }() 76 | 77 | logger.Infof("Fablet serve at %s.", addrPort) 78 | <-s 79 | logger.Info("Fablet server Exits.") 80 | } 81 | -------------------------------------------------------------------------------- /service/chaincode.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/IBM/fablet/api" 11 | "github.com/IBM/fablet/util" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | // ChaincodeInstallReq use fields instead of anonlymous fields, to have a more clear structure. 16 | type ChaincodeInstallReq struct { 17 | BaseRequest 18 | Chaincode api.Chaincode `json:"chaincode"` 19 | PackageFormat string `json:"packageFormat"` 20 | Targets []string `json:"targets"` 21 | } 22 | 23 | // ChaincodeInstantiateReq for instantiate a chaincode. 24 | type ChaincodeInstantiateReq struct { 25 | BaseRequest 26 | Chaincode api.Chaincode `json:"chaincode"` 27 | Target string `json:"target"` 28 | Orderer string `json:"orderer"` 29 | IsUpgrade bool `json:"isUpgrade"` 30 | } 31 | 32 | // ChaincodeExecuteReq to execute a chaincode 33 | type ChaincodeExecuteReq struct { 34 | BaseRequest 35 | Chaincode api.Chaincode `json:"chaincode"` 36 | ActionType string `json:"actionType"` 37 | FunctionName string `json:"functionName"` 38 | Arguments []string `json:"arguments"` 39 | Targets []string `json:"targets"` 40 | } 41 | 42 | // HandleChaincodeInstall to install a chaincode 43 | func HandleChaincodeInstall(res http.ResponseWriter, req *http.Request) { 44 | logger.Info("Service HandleChaincodeInstall") 45 | 46 | reqBody := &ChaincodeInstallReq{} 47 | conn, err := GetRequest(req, reqBody, true) 48 | if err != nil { 49 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 50 | return 51 | } 52 | 53 | // TODO remove tmp folder defer 54 | tmpFolder := GetTmpFolder() 55 | defer func() { 56 | err := os.RemoveAll(tmpFolder) 57 | if err != nil { 58 | logger.Errorf("Error in removing temp folder: %s", err.Error()) 59 | } 60 | }() 61 | 62 | logger.Debugf("Set temp folder %s", tmpFolder) 63 | 64 | chaincode := &reqBody.Chaincode 65 | 66 | // TODO to move this tar related to fablet api. 67 | err = util.UnTar(chaincode.Package, reqBody.PackageFormat, tmpFolder) 68 | if err != nil { 69 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when uncompress the chaincode package.")) 70 | return 71 | } 72 | 73 | if chaincode.Type == api.ChaincodeType_GOLANG { 74 | chaincode.BasePath = tmpFolder 75 | } else { 76 | // for Node and Java type chaincode 77 | chaincode.Path = tmpFolder 78 | } 79 | 80 | logger.Debugf(fmt.Sprintf("Begin to install %s:%s", chaincode.Name, chaincode.Version)) 81 | // installRes length will always be identical to the peers length. 82 | installRes, err := api.InstallChaincode(conn, chaincode, reqBody.Targets) 83 | 84 | if err != nil && installRes == nil { 85 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when installing the chaincode.")) 86 | return 87 | } 88 | 89 | // It might be: err is not nil but the reuslt is not nil also, since the installation was executed but got failed. 90 | ResultOutput(res, req, map[string]interface{}{ 91 | "installRes": installRes, 92 | }) 93 | 94 | } 95 | 96 | // HandleChaincodeInstantiate to instantiate a chaincode. 97 | func HandleChaincodeInstantiate(res http.ResponseWriter, req *http.Request) { 98 | logger.Info("Service HandleChaincodeInstantiate") 99 | reqBody := &ChaincodeInstantiateReq{} 100 | conn, err := GetRequest(req, reqBody, true) 101 | if err != nil { 102 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 103 | return 104 | } 105 | 106 | logger.Info(fmt.Sprintf("Begin to instantiate %s:%s", reqBody.Chaincode.Name, reqBody.Chaincode.Version)) 107 | 108 | transID, err := api.InstantiateChaincode(conn, &reqBody.Chaincode, reqBody.Target, reqBody.Orderer) 109 | 110 | if err != nil { 111 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when instantiating the chaincode.")) 112 | return 113 | } 114 | 115 | // It might be: err is not nil but the reuslt is not nil also, since the installation was executed but got failed. 116 | ResultOutput(res, req, map[string]interface{}{ 117 | "instantiateRes": string(transID), 118 | }) 119 | } 120 | 121 | // HandleChaincodeUpgrade to upgrade chaincode. 122 | func HandleChaincodeUpgrade(res http.ResponseWriter, req *http.Request) { 123 | // TODO to consolidate all same operations: reqBody, conn... 124 | logger.Info("Service HandleChaincodeUpgrade") 125 | body, err := ioutil.ReadAll(req.Body) 126 | if err != nil { 127 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when read from request.")) 128 | return 129 | } 130 | 131 | reqBody := &ChaincodeInstantiateReq{} 132 | err = json.Unmarshal(body, reqBody) 133 | if err != nil { 134 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when unmarshal ReqBody from request.")) 135 | return 136 | } 137 | 138 | reqConn := reqBody.Connection 139 | conn, err := GetConnection( 140 | // TODO to support multiple config file type 141 | &api.ConnectionProfile{Config: []byte(reqConn.ConnProfile), ConfigType: "yaml"}, 142 | &api.Participant{Label: reqConn.Label, OrgName: "", MSPID: reqConn.MSPID, 143 | Cert: []byte(reqConn.CertContent), PrivateKey: []byte(reqConn.PrvKeyContent), SignID: nil}, 144 | true) 145 | // TODO useDiscovery 146 | 147 | if err != nil { 148 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when create Fablet connection.")) 149 | return 150 | } 151 | 152 | logger.Info(fmt.Sprintf("Begin to upgrade %s:%s", reqBody.Chaincode.Name, reqBody.Chaincode.Version)) 153 | 154 | transID, err := api.UpgradeChaincode(conn, &reqBody.Chaincode, reqBody.Target, reqBody.Orderer) 155 | 156 | if err != nil { 157 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when upgrading the chaincode.")) 158 | return 159 | } 160 | 161 | // It might be: err is not nil but the reuslt is not nil also, since the installation was executed but got failed. 162 | ResultOutput(res, req, map[string]interface{}{ 163 | "upgradeRes": string(transID), 164 | }) 165 | 166 | } 167 | 168 | // HandleChaincodeExecute to execute a chaincode 169 | func HandleChaincodeExecute(res http.ResponseWriter, req *http.Request) { 170 | logger.Info("Service HandleChaincodeExecute") 171 | 172 | // body, err := ioutil.ReadAll(req.Body) 173 | // if err != nil { 174 | // ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when read from request.")) 175 | // return 176 | // } 177 | 178 | // reqBody := &ChaincodeExecuteReq{} 179 | // err = json.Unmarshal(body, reqBody) 180 | // if err != nil { 181 | // ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when unmarshal ReqBody from request.")) 182 | // return 183 | // } 184 | 185 | // reqConn := reqBody.Connection 186 | // conn, err := api.NewConnection( 187 | // // TODO to support multiple config file type 188 | // &api.ConnectionProfile{Config: []byte(reqConn.ConnProfile), ConfigType: "yaml"}, 189 | // &api.Participant{Label: reqConn.Label, OrgName: "", MSPID: reqConn.MSPID, 190 | // Cert: []byte(reqConn.CertContent), PrivateKey: []byte(reqConn.PrvKeyContent), SignID: nil}, 191 | // true) 192 | // TODO useDiscovery 193 | 194 | reqBody := &ChaincodeExecuteReq{} 195 | conn, err := GetRequest(req, reqBody, true) 196 | if err != nil { 197 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 198 | return 199 | } 200 | 201 | logger.Info(fmt.Sprintf("Begin to execute chaincode %s:%s", reqBody.Chaincode.Name, reqBody.Chaincode.Version)) 202 | 203 | ccOperType := api.ChaincodeOperTypeExecute 204 | if reqBody.ActionType == "query" { 205 | ccOperType = api.ChaincodeOperTypeQuery 206 | } 207 | 208 | // TODO target is not supported now 209 | cceRes, err := api.ExecuteChaincode(conn, reqBody.Chaincode.ChannelID, reqBody.Chaincode.Name, ccOperType, reqBody.Targets, reqBody.FunctionName, reqBody.Arguments) 210 | 211 | if err != nil { 212 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, 213 | errors.WithMessagef(err, "Error occurred when execute the chaincode %s in channel %s, with arguments %v, with targets %v.", 214 | reqBody.Chaincode.Name, reqBody.Chaincode.ChannelID, reqBody.Arguments, reqBody.Targets)) 215 | return 216 | } 217 | 218 | peerRes := []map[string]interface{}{} 219 | for _, pr := range cceRes.Responses { 220 | peerRes = append(peerRes, map[string]interface{}{ 221 | "endorser": pr.Endorser, 222 | "version": pr.GetVersion(), 223 | "payload": string(pr.GetResponse().GetPayload()), 224 | "status": pr.GetResponse().GetStatus(), 225 | }) 226 | } 227 | 228 | ResultOutput(res, req, map[string]interface{}{ 229 | "transactionID": cceRes.TransactionID, 230 | "txValidationCode": cceRes.TxValidationCode, 231 | "chaincodeStatus": cceRes.ChaincodeStatus, 232 | "payload": string(cceRes.Payload), 233 | "peerResponses": peerRes, 234 | }) 235 | } 236 | -------------------------------------------------------------------------------- /service/channel.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/IBM/fablet/api" 8 | ) 9 | 10 | // JoinChannelReq to join a channel 11 | type JoinChannelReq struct { 12 | BaseRequest 13 | ChannelID string `json:"channelID"` 14 | Targets []string `json:"targets"` 15 | Orderer string `json:"orderer"` 16 | } 17 | 18 | // CreateChannelReq to create a channel 19 | type CreateChannelReq struct { 20 | BaseRequest 21 | TxContent []byte `json:"txContent"` 22 | Orderer string `json:"orderer"` 23 | } 24 | 25 | // HandleCreateChannel to create a channle via orderer 26 | func HandleCreateChannel(res http.ResponseWriter, req *http.Request) { 27 | logger.Info("Service HandleCreateChannel") 28 | 29 | reqBody := &CreateChannelReq{} 30 | conn, err := GetRequest(req, reqBody, true) 31 | if err != nil { 32 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 33 | return 34 | } 35 | 36 | channelID, err := api.CreateChannel(conn, reqBody.TxContent, reqBody.Orderer) 37 | if err != nil { 38 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, 39 | errors.WithMessagef(err, "Error occurs when create channel %s via orderer %s.", channelID, reqBody.Orderer)) 40 | return 41 | } 42 | 43 | ResultOutput(res, req, map[string]interface{}{ 44 | "channelID": channelID, 45 | }) 46 | } 47 | 48 | // HandleJoinChannel for peer to join a channel 49 | func HandleJoinChannel(res http.ResponseWriter, req *http.Request) { 50 | logger.Info("Service HandleJoinChannel") 51 | 52 | reqBody := &JoinChannelReq{} 53 | conn, err := GetRequest(req, reqBody, true) 54 | if err != nil { 55 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 56 | return 57 | } 58 | 59 | err = api.JoinChannel(conn, reqBody.ChannelID, reqBody.Targets, reqBody.Orderer) 60 | 61 | if err != nil { 62 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, 63 | errors.WithMessagef(err, "Error occurs when peer %v join channel %s.", reqBody.Targets, reqBody.ChannelID)) 64 | return 65 | } 66 | 67 | ResultOutput(res, req, map[string]interface{}{ 68 | "channelID": reqBody.ChannelID, 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /service/common.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/IBM/fablet/api" 11 | "github.com/IBM/fablet/log" 12 | "github.com/pkg/errors" 13 | uuid "github.com/satori/go.uuid" 14 | ) 15 | 16 | var logger = log.GetLogger() 17 | var ExeFolder = GetExeFolder() 18 | 19 | // HTTPHandler To handle all incoming http request 20 | type HTTPHandler func(res http.ResponseWriter, req *http.Request) 21 | 22 | // Post to return HTTPHandler only process post method. 23 | func Post(hh HTTPHandler) HTTPHandler { 24 | return func(res http.ResponseWriter, req *http.Request) { 25 | if req.Method != http.MethodPost { 26 | PlainOutput(res, req, []byte("")) 27 | return 28 | } 29 | hh(res, req) 30 | } 31 | } 32 | 33 | // GetTmpFolder to get temp folder. 34 | func GetTmpFolder() string { 35 | return filepath.Join(ExeFolder, "tmp", uuid.NewV1().String()) 36 | } 37 | 38 | // GetExeFolder to get the folder of current executable binary file. 39 | func GetExeFolder() string { 40 | exe, err := os.Executable() 41 | if err != nil { 42 | return "" 43 | } 44 | return filepath.Dir(exe) 45 | } 46 | 47 | // RequestConnection request with connection 48 | type RequestConnection struct { 49 | Label string `json:"label"` 50 | MSPID string `json:"MSPID"` 51 | CertContent string `json:"certContent"` 52 | PrvKeyContent string `json:"prvKeyContent"` 53 | ConnProfile string `json:"connProfile"` 54 | } 55 | 56 | type Request interface { 57 | GetReqConn() *RequestConnection 58 | } 59 | 60 | type BaseRequest struct { 61 | Connection *RequestConnection `json:"connection"` 62 | } 63 | 64 | // GetReqConn interface function 65 | func (req *BaseRequest) GetReqConn() *RequestConnection { 66 | return req.Connection 67 | } 68 | 69 | // RequestOption optios for request. 70 | type RequestOption struct { 71 | Refresh bool 72 | } 73 | 74 | // RequestOptionFunc to handle the request option 75 | type RequestOptionFunc func(opt *RequestOption) error 76 | 77 | // WithRefresh only retrieve details of the peer. 78 | func WithRefresh(refresh bool) RequestOptionFunc { 79 | return func(opt *RequestOption) error { 80 | opt.Refresh = refresh 81 | return nil 82 | } 83 | } 84 | 85 | func generateOption(options ...RequestOptionFunc) *RequestOption { 86 | reqOpt := &RequestOption{} 87 | for _, opt := range options { 88 | opt(reqOpt) 89 | } 90 | return reqOpt 91 | } 92 | 93 | func SetHeader(res http.ResponseWriter, req *http.Request, addHeaders map[string]string) { 94 | header := res.Header() 95 | // TODO CORS is not safe now. 96 | header.Set("Access-Control-Allow-Origin", "*") 97 | header.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE") 98 | header.Set("Access-Control-Allow-Headers", "X-Requested-With,content-type") 99 | header.Set("Access-Control-Allow-Credentials", "true") 100 | if addHeaders != nil { 101 | for k, v := range addHeaders { 102 | header.Set(k, v) 103 | } 104 | } 105 | } 106 | 107 | func JsonOutput(res http.ResponseWriter, req *http.Request, result map[string]interface{}) { 108 | resultJSON, err := json.Marshal(result) 109 | if err != nil { 110 | logger.Error("Error occurs when marshal result to json.", err.Error()) 111 | } else { 112 | SetHeader(res, req, map[string]string{"Content-Type": "application/json;charset=utf-8"}) 113 | res.Write(resultJSON) 114 | } 115 | } 116 | 117 | // TODO all function should has return value. 118 | func ErrorOutput(res http.ResponseWriter, req *http.Request, resCode ResCode, err error) { 119 | result := map[string]interface{}{ 120 | "resCode": resCode, 121 | "errMsg": err.Error(), 122 | } 123 | logger.Error(err.Error()) 124 | JsonOutput(res, req, result) 125 | } 126 | 127 | func ResultOutput(res http.ResponseWriter, req *http.Request, result map[string]interface{}) { 128 | result["resCode"] = RES_CODE_OK 129 | JsonOutput(res, req, result) 130 | } 131 | 132 | func PlainOutput(res http.ResponseWriter, req *http.Request, content []byte) { 133 | SetHeader(res, req, map[string]string{"Content-Type": "text/plain;charset=utf-8"}) 134 | res.Write(content) 135 | } 136 | 137 | type ResCode int32 138 | 139 | const ( 140 | RES_CODE_ERR_INTERNAL = ResCode(500) 141 | RES_CODE_OK = ResCode(200) 142 | ) 143 | 144 | // GetRequest get request from http 145 | // TODO to use GetRequest for all services, and, all connections are not closed after calling, to hold connection in session. 146 | func GetRequest(req *http.Request, reqBody Request, useDiscovery bool, options ...RequestOptionFunc) (*api.NetworkConnection, error) { 147 | logger.Info("Service common function GetReuest") 148 | body, err := ioutil.ReadAll(req.Body) 149 | if err != nil { 150 | return nil, errors.WithMessage(err, "Error occurred when read from request.") 151 | } 152 | 153 | err = json.Unmarshal(body, reqBody) 154 | if err != nil { 155 | return nil, errors.WithMessage(err, "Error occurred when unmarshal ReqBody from request.") 156 | } 157 | 158 | reqConn := reqBody.GetReqConn() 159 | conn, err := getConnOfReq(reqConn, useDiscovery, options...) 160 | return conn, nil 161 | } 162 | 163 | func getConnOfReq(reqConn *RequestConnection, useDiscovery bool, options ...RequestOptionFunc) (*api.NetworkConnection, error) { 164 | opt := generateOption(options...) 165 | connFunc := GetConnection 166 | if opt.Refresh { 167 | connFunc = NewConnection 168 | } 169 | conn, err := connFunc( 170 | // TODO to support multiple config file type 171 | &api.ConnectionProfile{Config: []byte(reqConn.ConnProfile), ConfigType: "yaml"}, 172 | &api.Participant{Label: reqConn.Label, OrgName: "", MSPID: reqConn.MSPID, 173 | Cert: []byte(reqConn.CertContent), PrivateKey: []byte(reqConn.PrvKeyContent), SignID: nil}, 174 | useDiscovery) 175 | 176 | if err != nil { 177 | return nil, errors.WithMessage(err, "Error occurred when create Fablet connection.") 178 | } 179 | return conn, nil 180 | } 181 | -------------------------------------------------------------------------------- /service/event.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/IBM/fablet/api" 8 | "github.com/gorilla/websocket" 9 | "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // EventReq use fields instead of anonlymous fields, to have a more clear structure. 14 | type BlockEventReq struct { 15 | BaseRequest 16 | ChannelID string `json:"channelID"` 17 | } 18 | 19 | // ChaincodeEventReq chaincode event reqeust. 20 | type ChaincodeEventReq struct { 21 | BaseRequest 22 | ChannelID string `json:"channelID"` 23 | ChaincodeID string `json:"chaincodeID"` 24 | EventFilter string `json:"eventFilter"` 25 | } 26 | 27 | // BlockEventResult block event result 28 | type BlockEventResult struct { 29 | Number uint64 `json:"number"` 30 | TXNumber int `json:"TXNumber"` 31 | UpdateTime int64 `json:"updateTime"` 32 | SourceURL string `json:"sourceURL"` 33 | } 34 | 35 | // ChaincodeEventResult chaincode event result 36 | type ChaincodeEventResult struct { 37 | TXID string `json:"TXID"` 38 | ChaincodeID string `json:"chaincodeID"` 39 | EventName string `json:"eventName"` 40 | Payload string `json:"payload"` 41 | BlockNumber uint64 `json:"blockNumber"` 42 | SourceURL string `json:"sourceURL"` 43 | } 44 | 45 | type ErrorResult struct { 46 | Error string `json:error` 47 | } 48 | 49 | const ( 50 | // WSPingInterval interval of ping 51 | WSPingInterval = time.Second * 10 52 | // WSWriteDeadline deadline time of write 53 | WSWriteDeadline = time.Second * 10 54 | ) 55 | 56 | // HandleBlockEvent handle event 57 | func HandleBlockEvent(wsConn *websocket.Conn) error { 58 | logger.Info("Service HandleBlockEvent") 59 | reqBody := &BlockEventReq{} 60 | if err := wsConn.ReadJSON(reqBody); err != nil { 61 | return err 62 | } 63 | conn, err := getConnOfReq(reqBody.GetReqConn(), true) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | errChan := make(chan error, 1) 69 | // Monitor the client connection. 70 | go func() { 71 | for { 72 | logger.Debug("Begin to waiting for reading block event request") 73 | if _, _, err := wsConn.ReadMessage(); err != nil { 74 | // TODO To see if it err when client disconnected. 75 | logger.Errorf("Reading block event reqeust with error: %s.", err.Error()) 76 | errChan <- err 77 | return 78 | } 79 | } 80 | }() 81 | 82 | eventChan := make(chan *fab.FilteredBlockEvent, 1) 83 | closeChan := make(chan int, 1) 84 | eventCloseChan := make(chan int, 1) 85 | go api.MonitorBlockEvent(conn, reqBody.ChannelID, eventChan, closeChan, eventCloseChan) 86 | 87 | pingTicker := time.NewTicker(WSPingInterval) 88 | 89 | // TODO defer in sequence 90 | defer func() { 91 | logger.Info("Service HandleBlockEvent end") 92 | // TODO closeChan might be a little later, so then MonitorBlockEvent will ends a little later. 93 | closeChan <- 0 94 | pingTicker.Stop() 95 | }() 96 | 97 | for { 98 | select { 99 | case event := <-eventChan: 100 | logger.Debugf("Get a block event from %s.", event.SourceURL) 101 | // wsConn.SetWriteDeadline(time.Now().Add(WSWriteDeadline)) 102 | if wsConn == nil { 103 | return errors.Errorf("Websocket connection is nil.") 104 | } 105 | if event == nil { 106 | // Allow error 107 | // return errors.Errorf("Event is nil") 108 | } else { 109 | // TODO In fact, we don't know the block time now. 110 | blockEventRes := BlockEventResult{ 111 | Number: event.FilteredBlock.GetNumber(), 112 | TXNumber: len(event.FilteredBlock.GetFilteredTransactions()), 113 | UpdateTime: time.Now().UnixNano() / 1000000, 114 | SourceURL: event.SourceURL, 115 | } 116 | 117 | resultJSON, _ := json.Marshal(blockEventRes) 118 | if err := wsConn.WriteMessage(websocket.TextMessage, resultJSON); err != nil { 119 | return errors.WithMessage(err, "Error of write event data.") 120 | } 121 | } 122 | case err := <-errChan: 123 | return err 124 | case <-pingTicker.C: 125 | // TODO ping is not enough, there always be tens of seconds later after connection close. 126 | // Places a reader might be better. See the chaincode event. 127 | if wsConn == nil { 128 | return errors.Errorf("Websocket connection is nil.") 129 | } 130 | if err := wsConn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { 131 | return errors.WithMessage(err, "Error of write websocket ping.") 132 | } 133 | case <-eventCloseChan: 134 | return nil 135 | } 136 | } 137 | } 138 | 139 | // HandleChaincodeEvent handle event 140 | func HandleChaincodeEvent(wsConn *websocket.Conn) error { 141 | logger.Info("Service HandleChaincodeEvent") 142 | 143 | listeners := 0 144 | 145 | reqChan := make(chan *ChaincodeEventReq, 1) 146 | errChan := make(chan error, 1) 147 | 148 | eventChan := make(chan *fab.CCEvent, 1) 149 | closeChan := make(chan int, 1) 150 | eventCloseChan := make(chan error, 1) 151 | 152 | pingTicker := time.NewTicker(WSPingInterval) 153 | 154 | // TODO defer in sequence 155 | defer func() { 156 | logger.Info("Service HandleChaincodeEvent end") 157 | // TODO This should happen in context likes web service, then the event handler has chance to process closeChan. 158 | closeChan <- 0 159 | pingTicker.Stop() 160 | }() 161 | 162 | go readCCEventRequest(wsConn, reqChan, errChan) 163 | 164 | for { 165 | select { 166 | case reqBody := <-reqChan: 167 | logger.Debug("Received a reqBody and then begin a new event connection.") 168 | conn, err := getConnOfReq(reqBody.GetReqConn(), true) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | listeners++ 174 | 175 | // Close the existing event registration 176 | if listeners > 1 { 177 | closeChan <- 0 178 | } 179 | 180 | go api.MonitorChaincodeEvent(conn, reqBody.ChannelID, reqBody.ChaincodeID, reqBody.EventFilter, eventChan, closeChan, eventCloseChan) 181 | case err := <-errChan: 182 | return err 183 | case event := <-eventChan: 184 | logger.Debugf("Get a chaincode event from %s.", event.SourceURL) 185 | // wsConn.SetWriteDeadline(time.Now().Add(WSWriteDeadline)) 186 | if wsConn == nil { 187 | return errors.Errorf("Websocket connection is nil.") 188 | } 189 | if event == nil { 190 | // Allow error 191 | // return errors.Errorf("Event is nil") 192 | } else { 193 | ccEventRes := ChaincodeEventResult{ 194 | TXID: event.TxID, 195 | ChaincodeID: event.ChaincodeID, 196 | EventName: event.EventName, 197 | Payload: string(event.Payload), 198 | BlockNumber: event.BlockNumber, 199 | SourceURL: event.SourceURL, 200 | } 201 | resultJSON, _ := json.Marshal(ccEventRes) 202 | if err := wsConn.WriteMessage(websocket.TextMessage, resultJSON); err != nil { 203 | return errors.WithMessage(err, "Error of write event data.") 204 | } 205 | } 206 | case <-pingTicker.C: 207 | // logger.Debug("Ping................") 208 | // wsConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) 209 | // TODO ping is not enough, there always be 60 seconds later after connection close. 210 | if wsConn == nil { 211 | return errors.Errorf("Websocket connection is nil.") 212 | } 213 | // wsConn.SetWriteDeadline(time.Now().Add(WSWriteDeadline)) 214 | if err := wsConn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { 215 | return errors.WithMessage(err, "Error of write websocket ping.") 216 | } 217 | case <-eventCloseChan: 218 | // TODO It doesn't work yet - to use eventCloseChan to pass the error in API. 219 | 220 | // The error need to be handled by frontend, since the chaincode event filter might be changed. 221 | // Althought the block event don't need to to do this. 222 | // Anyway, the eventCloseChan might with error or nil. 223 | // if err != nil { 224 | // logger.Error("Chaincode event listener return error: %s.", err.Error()) 225 | // errRes := ErrorResult{ 226 | // Error: err.Error(), 227 | // } 228 | // resultJSON, _ := json.Marshal(errRes) 229 | // if msgErr := wsConn.WriteMessage(websocket.TextMessage, resultJSON); msgErr != nil { 230 | // return errors.WithMessage(err, "Error of write event data.") 231 | // } 232 | // } 233 | 234 | listeners-- 235 | if listeners <= 0 { 236 | return nil 237 | } 238 | } 239 | } 240 | 241 | } 242 | 243 | func readCCEventRequest(wsConn *websocket.Conn, reqChan chan *ChaincodeEventReq, errChan chan error) { 244 | for { 245 | logger.Debug("Begin to waiting for reading chaincode event request") 246 | reqBody := &ChaincodeEventReq{} 247 | 248 | if err := wsConn.ReadJSON(reqBody); err != nil { 249 | // TODO To see if it err when client disconnected. 250 | logger.Errorf("Reading chaincode event reqeust with error: %s.", err.Error()) 251 | errChan <- err 252 | return 253 | } 254 | 255 | logger.Errorf("Reading chaincode event reqeust: %s %s %s.", reqBody.ChannelID, reqBody.ChaincodeID, reqBody.EventFilter) 256 | reqChan <- reqBody 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /service/ledger.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/IBM/fablet/api" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // LedgerQueryReq to query a ledger 13 | type LedgerQueryReq struct { 14 | BaseRequest 15 | ChannelID string `json:"channelID"` 16 | Targets []string `json:"targets"` 17 | } 18 | 19 | // BlockQueryReq to query blocks 20 | type BlockQueryReq struct { 21 | BaseRequest 22 | ChannelID string `json:"channelID"` 23 | Targets []string `json:"targets"` 24 | Begin uint64 `json:"begin"` 25 | Len uint64 `json:"len"` 26 | } 27 | 28 | // BlockQueryAnyReq to query block with any: tx id, block hash, block number 29 | type BlockQueryAnyReq struct { 30 | BaseRequest 31 | ChannelID string `json:"channelID"` 32 | Targets []string `json:"targets"` 33 | QueryKey string `json:"queryKey"` 34 | } 35 | 36 | // HandleLedgerQuery to query a ledger of a channel 37 | func HandleLedgerQuery(res http.ResponseWriter, req *http.Request) { 38 | logger.Info("Service HandleLedgerQuery") 39 | 40 | reqBody := &LedgerQueryReq{} 41 | conn, err := GetRequest(req, reqBody, true) 42 | if err != nil { 43 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 44 | return 45 | } 46 | 47 | logger.Info(fmt.Sprintf("Begin to query ledger at %v of channel %s", reqBody.Targets, reqBody.ChannelID)) 48 | 49 | ledgerRes, err := api.QueryLedger(conn, reqBody.ChannelID, reqBody.Targets) 50 | if err != nil { 51 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when query the ledger.")) 52 | return 53 | } 54 | 55 | ResultOutput(res, req, map[string]interface{}{ 56 | "ledger": ledgerRes, 57 | }) 58 | } 59 | 60 | // HandleBlockQuery to query blocks of a ledger 61 | func HandleBlockQuery(res http.ResponseWriter, req *http.Request) { 62 | logger.Info("Service HandleBlockQuery") 63 | 64 | reqBody := &BlockQueryReq{} 65 | conn, err := GetRequest(req, reqBody, true) 66 | if err != nil { 67 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 68 | return 69 | } 70 | 71 | blocks, _ := api.QueryBlock(conn, reqBody.ChannelID, reqBody.Targets, reqBody.Begin, reqBody.Len) 72 | ResultOutput(res, req, map[string]interface{}{ 73 | "blocks": blocks, 74 | }) 75 | } 76 | 77 | // HandleBlockQueryAny to query a block of a ledger by any possible kind of key 78 | func HandleBlockQueryAny(res http.ResponseWriter, req *http.Request) { 79 | logger.Info("Service HandleBlockQueryAny") 80 | 81 | reqBody := &BlockQueryAnyReq{} 82 | conn, err := GetRequest(req, reqBody, true) 83 | if err != nil { 84 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 85 | return 86 | } 87 | 88 | var block *api.Block 89 | 90 | blkNumber, err := strconv.ParseUint(reqBody.QueryKey, 10, 64) 91 | if err == nil { 92 | blocks, err := api.QueryBlock(conn, reqBody.ChannelID, reqBody.Targets, blkNumber, 1) 93 | if err == nil && len(blocks) > 0 { 94 | block = blocks[0] 95 | } 96 | } else { 97 | block, err = api.QueryBlockByHash(conn, reqBody.ChannelID, reqBody.Targets, reqBody.QueryKey) 98 | if err != nil { 99 | block, err = api.QueryBlockByTxID(conn, reqBody.ChannelID, reqBody.Targets, reqBody.QueryKey) 100 | } 101 | } 102 | 103 | if err != nil { 104 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.Errorf("Error occurred when query the block, cannot find the block by number, hash or transaction ID.")) 105 | return 106 | } 107 | 108 | ResultOutput(res, req, map[string]interface{}{ 109 | "block": block, 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /service/network.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/IBM/fablet/api" 7 | "github.com/IBM/fablet/util" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // NetworkDiscoverReq discover request 12 | type NetworkDiscoverReq struct { 13 | BaseRequest 14 | } 15 | 16 | // NetworkRefreshReq discover request. Same with NetworkDiscoverReq, but it will includes refresh=true. 17 | type NetworkRefreshReq struct { 18 | BaseRequest 19 | } 20 | 21 | // HandleNetworkDiscover to discover all network 22 | func HandleNetworkDiscover(res http.ResponseWriter, req *http.Request) { 23 | logger.Info("Service HandleNetworkDiscover") 24 | 25 | reqBody := &NetworkDiscoverReq{} 26 | conn, err := GetRequest(req, reqBody, true) 27 | if err != nil { 28 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 29 | return 30 | } 31 | 32 | networkOverview, err := api.DiscoverNetworkOverview(conn) 33 | if err != nil { 34 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurs when get network overview.")) 35 | return 36 | } 37 | 38 | ResultOutput(res, req, map[string]interface{}{ 39 | "peers": networkOverview.Peers, 40 | "channels": transChannels(networkOverview.Channels), 41 | "peerStatuses": transPeerStatuses(networkOverview.EndpointStatuses), 42 | "channelLedgers": networkOverview.ChannelLedgers, 43 | "channelChaincodes": networkOverview.ChannelChainCodes, 44 | "channelOrderers": networkOverview.ChannelOrderers, 45 | "channelAnchorPeers": networkOverview.ChannelAnchorPeers, 46 | }) 47 | } 48 | 49 | func transChannels(channels []*api.Channel) []string { 50 | channelIDs := []string{} 51 | for _, channel := range channels { 52 | channelIDs = append(channelIDs, channel.ChannelID) 53 | } 54 | return channelIDs 55 | } 56 | 57 | func transPeerStatuses(statuses map[string]util.EndPointStatus) map[string]api.PeerStatus { 58 | transStatuses := map[string]api.PeerStatus{} 59 | 60 | for peer, status := range statuses { 61 | transStatus := api.PeerStatus{ 62 | Ping: true, 63 | GRPC: true, 64 | Valid: true, 65 | } 66 | 67 | if status != util.EndPointStatus_Valid { 68 | transStatus.Valid = false 69 | if status != util.EndPointStatus_Connectable { 70 | if status == util.EndPointStatus_Refused { 71 | transStatus.GRPC = false 72 | } else { 73 | // Not found or timeout 74 | transStatus.GRPC = false 75 | transStatus.Ping = false 76 | } 77 | } 78 | } 79 | 80 | transStatuses[peer] = transStatus 81 | } 82 | 83 | return transStatuses 84 | } 85 | 86 | // HandleNetworkRefresh to discover all network 87 | func HandleNetworkRefresh(res http.ResponseWriter, req *http.Request) { 88 | logger.Info("Service HandleNetworkRefresh") 89 | 90 | reqBody := &NetworkRefreshReq{} 91 | conn, err := GetRequest(req, reqBody, true, WithRefresh(true)) 92 | if err != nil { 93 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 94 | return 95 | } 96 | ResultOutput(res, req, map[string]interface{}{ 97 | "conn": conn.Identifier, 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /service/network_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/IBM/fablet/util" 7 | ) 8 | 9 | func TestPeerStatus(t *testing.T) { 10 | statuses := map[string]util.EndPointStatus{ 11 | "Timeout": util.EndPointStatus_Timeout, 12 | "Notfound": util.EndPointStatus_NotFound, 13 | "Refused": util.EndPointStatus_Refused, 14 | "Connectable": util.EndPointStatus_Connectable, 15 | "Valid": util.EndPointStatus_Valid, 16 | } 17 | transStatuses := transPeerStatuses(statuses) 18 | for peer, status := range transStatuses { 19 | t.Log(peer, status) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/peer.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/IBM/fablet/api" 8 | ) 9 | 10 | // PeerDetailsReq to get details of a peer 11 | type PeerDetailsReq struct { 12 | BaseRequest 13 | Target string `json:"target"` 14 | Channels []string `json:"channels"` 15 | } 16 | 17 | // HandlePeerDetails for peer details 18 | func HandlePeerDetails(res http.ResponseWriter, req *http.Request) { 19 | logger.Info("Service HandlePeerDetails") 20 | 21 | reqBody := &PeerDetailsReq{} 22 | conn, err := GetRequest(req, reqBody, true) 23 | if err != nil { 24 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessage(err, "Error occurred when parsing from request.")) 25 | return 26 | } 27 | 28 | // Try to update channels 29 | channels, err := api.GetJoinedChannels(conn, reqBody.Target) 30 | channelIDs := reqBody.Channels 31 | if err == nil { 32 | channelIDs = []string{} 33 | for _, channel := range channels { 34 | channelIDs = append(channelIDs, channel.GetChannelId()) 35 | } 36 | } 37 | 38 | installedCCQueryError := false 39 | installedChaincodes, err := api.QueryInstalledChaincodes(conn, reqBody.Target) 40 | if err != nil { 41 | installedCCQueryError = true 42 | //ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, errors.WithMessagef(err, "Error occurs when get peer installed chaincodes of %s.", reqBody.Target)) 43 | //return 44 | } 45 | 46 | channelChaincodes := make(map[string][]*api.Chaincode) 47 | for _, channelID := range channelIDs { 48 | instantiatedChaincodes, err := api.QueryInstantiatedChaincodes(conn, channelID) 49 | if err == nil { 50 | channelChaincodes[channelID] = instantiatedChaincodes 51 | } 52 | if err != nil { 53 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, 54 | errors.WithMessagef(err, "Error occurs when get instantiated chaincodes of %s of channel %s.", reqBody.Target, channelID)) 55 | //return 56 | } 57 | } 58 | 59 | channelLedgers := make(map[string]*api.Ledger) 60 | for _, channelID := range channelIDs { 61 | ledger, err := api.QueryLedger(conn, channelID, []string{reqBody.Target}) 62 | if err == nil { 63 | channelLedgers[channelID] = ledger 64 | } 65 | if err != nil { 66 | ErrorOutput(res, req, RES_CODE_ERR_INTERNAL, 67 | errors.WithMessagef(err, "Error occurs when get channels of %s.", reqBody.Target)) 68 | //return 69 | } 70 | } 71 | 72 | ResultOutput(res, req, map[string]interface{}{ 73 | "installedChaincodes": installedChaincodes, // Per peer 74 | "channelChaincodes": channelChaincodes, // Per channel 75 | "installedCCQueryError": installedCCQueryError, 76 | "channelLedgers": channelLedgers, 77 | "channels": channelIDs, 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /service/root.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func HandleRoot(res http.ResponseWriter, req *http.Request) { 8 | PlainOutput(res, req, []byte("Fablet is running!")) 9 | } 10 | -------------------------------------------------------------------------------- /service/session.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/IBM/fablet/api" 8 | ) 9 | 10 | const ( 11 | // ConnMonitorInterval to check the connection every interval. 12 | ConnMonitorInterval = time.Second * 30 13 | // ConnInactiveLongest the connection will be closed and removed if be not actived for longer than the inactive time. 14 | ConnInactiveLongest = time.Minute * 10 15 | ) 16 | 17 | // ConnSession to store all connections. 18 | // TODO to have NetworkConnection with lock. *** 19 | type ConnSession struct { 20 | Connections map[string]*api.NetworkConnection 21 | sync.RWMutex 22 | } 23 | 24 | // FindConn find connection from session. 25 | func (connSession *ConnSession) findConn(id string) (*api.NetworkConnection, bool) { 26 | conn, ok := connSession.Connections[id] 27 | return conn, ok 28 | } 29 | 30 | func (connSession *ConnSession) storeConn(conn *api.NetworkConnection) { 31 | connSession.Lock() 32 | defer connSession.Unlock() 33 | 34 | // Double check, to avoid concurrent storing. 35 | // This might cause exception if the tmpConn is being used, but it is not critical. 36 | // TODO 37 | if tmpConn, ok := connSession.findConn(conn.Identifier); ok { 38 | logger.Debugf("Double check and found stored connection of %s.", conn.Identifier) 39 | tmpConn.Close() 40 | } 41 | connSession.Connections[conn.Identifier] = conn 42 | } 43 | 44 | func (connSession *ConnSession) removeConn(id string) { 45 | connSession.Lock() 46 | defer connSession.Unlock() 47 | if conn, ok := connSession.findConn(id); ok { 48 | // TODO It might be not safe here, the conn might be in using. 49 | conn.Close() 50 | delete(connSession.Connections, id) 51 | } 52 | } 53 | 54 | // A globla variable. 55 | var connSession *ConnSession 56 | 57 | // Init of package level. 58 | func init() { 59 | logger.Info("Initialize the connection store.") 60 | connSession = &ConnSession{ 61 | Connections: make(map[string]*api.NetworkConnection), 62 | } 63 | go monitorConnSession() 64 | } 65 | 66 | func monitorConnSession() { 67 | tk := time.NewTicker(ConnMonitorInterval) 68 | for t := range tk.C { 69 | for id, conn := range connSession.Connections { 70 | afterActive := time.Since(conn.ActiveTime) 71 | if afterActive > ConnInactiveLongest { 72 | logger.Infof("Connection %s after active time: %v (now %v), so then to be closed and removed.", id, afterActive, t) 73 | go connSession.removeConn(id) 74 | } else { 75 | logger.Infof("Connection %s after active time: %v.", id, afterActive) 76 | } 77 | } 78 | } 79 | } 80 | 81 | // GetConnection get connection from session, might be existing or new. 82 | func GetConnection(connProfile *api.ConnectionProfile, participant *api.Participant, useDiscovery bool) (*api.NetworkConnection, error) { 83 | id := string(api.CalConnIdentifier(connProfile, participant, useDiscovery)) 84 | if conn, ok := connSession.findConn(id); ok { 85 | logger.Debugf("Find stored connection of %s.", id) 86 | conn.ActiveTime = time.Now() 87 | return conn, nil 88 | } 89 | 90 | return NewConnection(connProfile, participant, useDiscovery) 91 | } 92 | 93 | // NewConnection create connection and then store it into session. 94 | func NewConnection(connProfile *api.ConnectionProfile, participant *api.Participant, useDiscovery bool) (*api.NetworkConnection, error) { 95 | conn, err := api.NewConnection(connProfile, participant, useDiscovery) 96 | if err == nil { 97 | logger.Debugf("Store new connection of %s.", conn.Identifier) 98 | // Might be discarded in storeConn function, but not critical. 99 | go connSession.storeConn(conn) 100 | return conn, nil 101 | } 102 | return nil, err 103 | } 104 | -------------------------------------------------------------------------------- /service/websocket.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | ) 8 | 9 | var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} 10 | 11 | // WSHandler To handle all incoming http request 12 | type WSHandler func(wsConn *websocket.Conn) error 13 | 14 | // WS to return websocket handler 15 | func WS(wsh WSHandler) HTTPHandler { 16 | return func(res http.ResponseWriter, req *http.Request) { 17 | logger.Infof("Websocket starts.") 18 | 19 | wsConn, err := upgrader.Upgrade(res, req, nil) 20 | if err != nil { 21 | logger.Errorf("Error in websocket: %s", err.Error()) 22 | return 23 | } 24 | defer func() { 25 | logger.Infof("Websocket quits.") 26 | wsConn.Close() 27 | }() 28 | 29 | if err := wsh(wsConn); err != nil { 30 | logger.Errorf("Error in websocket: %s", err.Error()) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/chaincodes/example02java/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright IBM Corp. 2018 All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | plugins { 7 | id 'com.github.johnrengelman.shadow' version '2.0.3' 8 | id 'java' 9 | } 10 | 11 | group 'org.hyperledger.fabric-chaincode-java' 12 | version '1.0-SNAPSHOT' 13 | 14 | sourceCompatibility = 1.8 15 | 16 | repositories { 17 | mavenLocal() 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | compile group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '1.+' 23 | testCompile group: 'junit', name: 'junit', version: '4.12' 24 | } 25 | 26 | shadowJar { 27 | baseName = 'chaincode' 28 | version = null 29 | classifier = null 30 | 31 | manifest { 32 | attributes 'Main-Class': 'org.hyperledger.fabric.example.SimpleChaincode' 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/chaincodes/example02java/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright IBM Corp. 2017 All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | rootProject.name = 'fabric-chaincode-example-gradle' 7 | 8 | -------------------------------------------------------------------------------- /test/chaincodes/example02java/src/main/java/org/hyperledger/fabric/example/SimpleChaincode.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp., DTCC All Rights Reserved. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | */ 6 | package org.hyperledger.fabric.example; 7 | 8 | import java.util.List; 9 | 10 | import com.google.protobuf.ByteString; 11 | import io.netty.handler.ssl.OpenSsl; 12 | import org.apache.commons.logging.Log; 13 | import org.apache.commons.logging.LogFactory; 14 | import org.hyperledger.fabric.shim.ChaincodeBase; 15 | import org.hyperledger.fabric.shim.ChaincodeStub; 16 | 17 | import static java.nio.charset.StandardCharsets.UTF_8; 18 | 19 | public class SimpleChaincode extends ChaincodeBase { 20 | 21 | private static Log _logger = LogFactory.getLog(SimpleChaincode.class); 22 | 23 | @Override 24 | public Response init(ChaincodeStub stub) { 25 | try { 26 | _logger.info("Init java simple chaincode"); 27 | String func = stub.getFunction(); 28 | if (!func.equals("init")) { 29 | return newErrorResponse("function other than init is not supported"); 30 | } 31 | List args = stub.getParameters(); 32 | if (args.size() != 4) { 33 | newErrorResponse("Incorrect number of arguments. Expecting 4"); 34 | } 35 | // Initialize the chaincode 36 | String account1Key = args.get(0); 37 | int account1Value = Integer.parseInt(args.get(1)); 38 | String account2Key = args.get(2); 39 | int account2Value = Integer.parseInt(args.get(3)); 40 | 41 | _logger.info(String.format("account %s, value = %s; account %s, value %s", account1Key, account1Value, account2Key, account2Value)); 42 | stub.putStringState(account1Key, args.get(1)); 43 | stub.putStringState(account2Key, args.get(3)); 44 | 45 | return newSuccessResponse(); 46 | } catch (Throwable e) { 47 | return newErrorResponse(e); 48 | } 49 | } 50 | 51 | @Override 52 | public Response invoke(ChaincodeStub stub) { 53 | try { 54 | _logger.info("Invoke java simple chaincode"); 55 | String func = stub.getFunction(); 56 | List params = stub.getParameters(); 57 | if (func.equals("invoke")) { 58 | return invoke(stub, params); 59 | } 60 | if (func.equals("delete")) { 61 | return delete(stub, params); 62 | } 63 | if (func.equals("query")) { 64 | return query(stub, params); 65 | } 66 | return newErrorResponse("Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]"); 67 | } catch (Throwable e) { 68 | return newErrorResponse(e); 69 | } 70 | } 71 | 72 | private Response invoke(ChaincodeStub stub, List args) { 73 | if (args.size() != 3) { 74 | return newErrorResponse("Incorrect number of arguments. Expecting 3"); 75 | } 76 | String accountFromKey = args.get(0); 77 | String accountToKey = args.get(1); 78 | 79 | String accountFromValueStr = stub.getStringState(accountFromKey); 80 | if (accountFromValueStr == null) { 81 | return newErrorResponse(String.format("Entity %s not found", accountFromKey)); 82 | } 83 | int accountFromValue = Integer.parseInt(accountFromValueStr); 84 | 85 | String accountToValueStr = stub.getStringState(accountToKey); 86 | if (accountToValueStr == null) { 87 | return newErrorResponse(String.format("Entity %s not found", accountToKey)); 88 | } 89 | int accountToValue = Integer.parseInt(accountToValueStr); 90 | 91 | int amount = Integer.parseInt(args.get(2)); 92 | 93 | if (amount > accountFromValue) { 94 | return newErrorResponse(String.format("not enough money in account %s", accountFromKey)); 95 | } 96 | 97 | accountFromValue -= amount; 98 | accountToValue += amount; 99 | 100 | _logger.info(String.format("new value of A: %s", accountFromValue)); 101 | _logger.info(String.format("new value of B: %s", accountToValue)); 102 | 103 | stub.putStringState(accountFromKey, Integer.toString(accountFromValue)); 104 | stub.putStringState(accountToKey, Integer.toString(accountToValue)); 105 | 106 | _logger.info("Transfer complete"); 107 | 108 | return newSuccessResponse("invoke finished successfully", ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " " + accountToKey + ": " + accountToValue, UTF_8).toByteArray()); 109 | } 110 | 111 | // Deletes an entity from state 112 | private Response delete(ChaincodeStub stub, List args) { 113 | if (args.size() != 1) { 114 | return newErrorResponse("Incorrect number of arguments. Expecting 1"); 115 | } 116 | String key = args.get(0); 117 | // Delete the key from the state in ledger 118 | stub.delState(key); 119 | return newSuccessResponse(); 120 | } 121 | 122 | // query callback representing the query of a chaincode 123 | private Response query(ChaincodeStub stub, List args) { 124 | if (args.size() != 1) { 125 | return newErrorResponse("Incorrect number of arguments. Expecting name of the person to query"); 126 | } 127 | String key = args.get(0); 128 | //byte[] stateBytes 129 | String val = stub.getStringState(key); 130 | if (val == null) { 131 | return newErrorResponse(String.format("Error: state for %s is null", key)); 132 | } 133 | _logger.info(String.format("Query Response:\nName: %s, Amount: %s\n", key, val)); 134 | return newSuccessResponse(val, ByteString.copyFrom(val, UTF_8).toByteArray()); 135 | } 136 | 137 | public static void main(String[] args) { 138 | System.out.println("OpenSSL avaliable: " + OpenSsl.isAvailable()); 139 | new SimpleChaincode().start(args); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /test/chaincodes/example02node/src/chaincode_example02.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright IBM Corp. All Rights Reserved. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | const shim = require('fabric-shim'); 8 | const util = require('util'); 9 | 10 | var Chaincode = class { 11 | 12 | // Initialize the chaincode 13 | async Init(stub) { 14 | console.info('========= example02 Init ========='); 15 | let ret = stub.getFunctionAndParameters(); 16 | console.info(ret); 17 | let args = ret.params; 18 | // initialise only if 4 parameters passed. 19 | if (args.length != 4) { 20 | return shim.error('Incorrect number of arguments. Expecting 4'); 21 | } 22 | 23 | let A = args[0]; 24 | let B = args[2]; 25 | let Aval = args[1]; 26 | let Bval = args[3]; 27 | 28 | if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') { 29 | return shim.error('Expecting integer value for asset holding'); 30 | } 31 | 32 | try { 33 | await stub.putState(A, Buffer.from(Aval)); 34 | try { 35 | await stub.putState(B, Buffer.from(Bval)); 36 | return shim.success(); 37 | } catch (err) { 38 | return shim.error(err); 39 | } 40 | } catch (err) { 41 | return shim.error(err); 42 | } 43 | } 44 | 45 | async Invoke(stub) { 46 | let ret = stub.getFunctionAndParameters(); 47 | console.info(ret); 48 | let method = this[ret.fcn]; 49 | if (!method) { 50 | console.log('no method of name:' + ret.fcn + ' found'); 51 | return shim.success(); 52 | } 53 | try { 54 | let payload = await method(stub, ret.params); 55 | return shim.success(payload); 56 | } catch (err) { 57 | console.log(err); 58 | return shim.error(err); 59 | } 60 | } 61 | 62 | async invoke(stub, args) { 63 | if (args.length != 3) { 64 | throw new Error('Incorrect number of arguments. Expecting 3'); 65 | } 66 | 67 | let A = args[0]; 68 | let B = args[1]; 69 | if (!A || !B) { 70 | throw new Error('asset holding must not be empty'); 71 | } 72 | 73 | // Get the state from the ledger 74 | let Avalbytes = await stub.getState(A); 75 | if (!Avalbytes) { 76 | throw new Error('Failed to get state of asset holder A'); 77 | } 78 | let Aval = parseInt(Avalbytes.toString()); 79 | 80 | let Bvalbytes = await stub.getState(B); 81 | if (!Bvalbytes) { 82 | throw new Error('Failed to get state of asset holder B'); 83 | } 84 | 85 | let Bval = parseInt(Bvalbytes.toString()); 86 | // Perform the execution 87 | let amount = parseInt(args[2]); 88 | if (typeof amount !== 'number') { 89 | throw new Error('Expecting integer value for amount to be transaferred'); 90 | } 91 | 92 | Aval = Aval - amount; 93 | Bval = Bval + amount; 94 | console.info(util.format('Aval = %d, Bval = %d\n', Aval, Bval)); 95 | 96 | // Write the states back to the ledger 97 | await stub.putState(A, Buffer.from(Aval.toString())); 98 | await stub.putState(B, Buffer.from(Bval.toString())); 99 | 100 | } 101 | 102 | // Deletes an entity from state 103 | async delete(stub, args) { 104 | if (args.length != 1) { 105 | throw new Error('Incorrect number of arguments. Expecting 1'); 106 | } 107 | 108 | let A = args[0]; 109 | 110 | // Delete the key from the state in ledger 111 | await stub.deleteState(A); 112 | } 113 | 114 | // query callback representing the query of a chaincode 115 | async query(stub, args) { 116 | if (args.length != 1) { 117 | throw new Error('Incorrect number of arguments. Expecting name of the person to query') 118 | } 119 | 120 | let jsonResp = {}; 121 | let A = args[0]; 122 | 123 | // Get the state from the ledger 124 | let Avalbytes = await stub.getState(A); 125 | if (!Avalbytes) { 126 | jsonResp.error = 'Failed to get state for ' + A; 127 | throw new Error(JSON.stringify(jsonResp)); 128 | } 129 | 130 | jsonResp.name = A; 131 | jsonResp.amount = Avalbytes.toString(); 132 | console.info('Query Response:'); 133 | console.info(jsonResp); 134 | return Avalbytes; 135 | } 136 | }; 137 | 138 | shim.start(new Chaincode()); 139 | -------------------------------------------------------------------------------- /test/chaincodes/example02node/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chaincode_example02", 3 | "version": "1.0.0", 4 | "description": "chaincode_example02 chaincode implemented in node.js", 5 | "engines": { 6 | "node": ">=8.4.0", 7 | "npm": ">=5.3.0" 8 | }, 9 | "scripts": { 10 | "start": "node chaincode_example02.js" 11 | }, 12 | "engine-strict": true, 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "fabric-shim": "~1.4.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/chaincodes/vehiclesharing/src/fablet/vs/META-INF/statedb/couchdb/indexes/indexBrand.json: -------------------------------------------------------------------------------- 1 | {"index":{"fields":["objectType","brand"]},"ddoc":"indexBrandDoc", "name":"indexBrand","type":"json"} 2 | -------------------------------------------------------------------------------- /test/chaincodes/vehiclesharing/src/fablet/vs/META-INF/statedb/couchdb/indexes/indexId.json: -------------------------------------------------------------------------------- 1 | {"index":{"fields":["objectType","id"]},"ddoc":"indexIdDoc", "name":"indexId","type":"json"} 2 | -------------------------------------------------------------------------------- /test/chaincodes/vehiclesharing/src/fablet/vs/collections_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "leaseRecords", 4 | "policy": "OR('Org1MSP.member', 'Org2MSP.member')", 5 | "requiredPeerCount": 2, 6 | "maxPeerCount": 4, 7 | "blockToLive":3, 8 | "memberOnlyRead": true 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/chaincodes/vs_src.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/test/chaincodes/vs_src.tar.gz -------------------------------------------------------------------------------- /test/connprofiles/conn_profile_pre.yaml: -------------------------------------------------------------------------------- 1 | name: "simplest_with_discover" 2 | version: 1.0.0 3 | organizations: 4 | Org1: 5 | mspid: Org1MSP 6 | cryptoPath: ./msp 7 | peers: 8 | - peer0.org1.example.com 9 | Org2: 10 | mspid: Org2MSP 11 | cryptoPath: ./msp 12 | peers: 13 | - peer0.org2.example.com 14 | 15 | # For instantiate cc 16 | channels: 17 | mychannel: 18 | peers: 19 | peer0.org1.example.com: 20 | endorsingPeer: true 21 | chaincodeQuery: true 22 | ledgerQuery: true 23 | eventSource: true 24 | peer0.org2.example.com: 25 | endorsingPeer: true 26 | chaincodeQuery: true 27 | ledgerQuery: true 28 | eventSource: true 29 | 30 | orderers: 31 | orderer.example.com: 32 | url: localhost:7050 33 | grpcOptions: 34 | ssl-target-name-override: orderer.example.com 35 | tlsCACerts: 36 | pem: | 37 | -----BEGIN CERTIFICATE----- 38 | MIICQjCCAemgAwIBAgIQCYo1lSmX4ecpmmfH7GEZcjAKBggqhkjOPQQDAjBsMQsw 39 | CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy 40 | YW5jaXNjbzEUMBIGA1UEChMLZXhhbXBsZS5jb20xGjAYBgNVBAMTEXRsc2NhLmV4 41 | YW1wbGUuY29tMB4XDTIwMDIyMTEwMDcwMFoXDTMwMDIxODEwMDcwMFowbDELMAkG 42 | A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFu 43 | Y2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRowGAYDVQQDExF0bHNjYS5leGFt 44 | cGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIYnILVrIOcBYJPFtIuq 45 | Y8Sid5chbp6HLAo+53IebAcP9g2TkD9LMuo8BrnHD//YgAq0uTWVvD0gJFiiOABy 46 | RlyjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYB 47 | BQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgAu0Y2+1f8xwzS5S0iogb 48 | QUbw/QvzQ2NMim8QMQggjbQwCgYIKoZIzj0EAwIDRwAwRAIgYC80OVsYlrt9yf9L 49 | LXfzPr5nkLG7EQrXkd4NBLT/nT4CICFmJ9eVNCwOwUbauy4zuaOEeFP3x08mye8x 50 | BaB2I8JH 51 | -----END CERTIFICATE----- 52 | orderer2.example.com: 53 | url: localhost:8050 54 | grpcOptions: 55 | ssl-target-name-override: orderer2.example.com 56 | tlsCACerts: 57 | pem: | 58 | -----BEGIN CERTIFICATE----- 59 | MIICQjCCAemgAwIBAgIQCYo1lSmX4ecpmmfH7GEZcjAKBggqhkjOPQQDAjBsMQsw 60 | CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy 61 | YW5jaXNjbzEUMBIGA1UEChMLZXhhbXBsZS5jb20xGjAYBgNVBAMTEXRsc2NhLmV4 62 | YW1wbGUuY29tMB4XDTIwMDIyMTEwMDcwMFoXDTMwMDIxODEwMDcwMFowbDELMAkG 63 | A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFu 64 | Y2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRowGAYDVQQDExF0bHNjYS5leGFt 65 | cGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIYnILVrIOcBYJPFtIuq 66 | Y8Sid5chbp6HLAo+53IebAcP9g2TkD9LMuo8BrnHD//YgAq0uTWVvD0gJFiiOABy 67 | RlyjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYB 68 | BQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgAu0Y2+1f8xwzS5S0iogb 69 | QUbw/QvzQ2NMim8QMQggjbQwCgYIKoZIzj0EAwIDRwAwRAIgYC80OVsYlrt9yf9L 70 | LXfzPr5nkLG7EQrXkd4NBLT/nT4CICFmJ9eVNCwOwUbauy4zuaOEeFP3x08mye8x 71 | BaB2I8JH 72 | -----END CERTIFICATE----- 73 | ############ 74 | 75 | peers: 76 | peer0.org1.example.com: 77 | url: peer0.org1.example.com:7051 78 | eventUrl: peer0.org1.example.com:7053 79 | grpcOptions: 80 | ssl-target-name-override: peer0.org1.example.com 81 | tlsCACerts: 82 | pem: | 83 | -----BEGIN CERTIFICATE----- 84 | MIICVzCCAf6gAwIBAgIRAMv2GfiYQXx7WfknTWg0ZBwwCgYIKoZIzj0EAwIwdjEL 85 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 86 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs 87 | c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjIxMTAwNzAwWhcNMzAwMjE4MTAw 88 | NzAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE 89 | BxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G 90 | A1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 91 | AwEHA0IABL7G3+3hAsTyDwlqwt+DjByElGH4wbfDPBdgvnBR4NuF5gk0KzEgHhLz 92 | /Z1nTxyM0TcQuuGqssMEHE+MYvBnyFCjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV 93 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV 94 | HQ4EIgQghTYOpq3gnj0xvs8871nl6SEvyqWDVruSTeYHoc6NL8owCgYIKoZIzj0E 95 | AwIDRwAwRAIgakIxxTb+uxE+VCKHh5UXum8COyMsuCagkm3oT0Ahvu8CIGDdSJmh 96 | KUWDfdoihW+IkVFjntfdknCNVsQ0Awlm0No6 97 | -----END CERTIFICATE----- 98 | peer0.org2.example.com: 99 | url: peer0.org2.example.com:9051 100 | eventUrl: peer0.org2.example.com:9053 101 | grpcOptions: 102 | ssl-target-name-override: peer0.org2.example.com 103 | tlsCACerts: 104 | pem: | 105 | -----BEGIN CERTIFICATE----- 106 | MIICVzCCAf6gAwIBAgIRAMv2GfiYQXx7WfknTWg0ZBwwCgYIKoZIzj0EAwIwdjEL 107 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 108 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs 109 | c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjIxMTAwNzAwWhcNMzAwMjE4MTAw 110 | NzAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE 111 | BxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G 112 | A1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 113 | AwEHA0IABL7G3+3hAsTyDwlqwt+DjByElGH4wbfDPBdgvnBR4NuF5gk0KzEgHhLz 114 | /Z1nTxyM0TcQuuGqssMEHE+MYvBnyFCjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV 115 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV 116 | HQ4EIgQghTYOpq3gnj0xvs8871nl6SEvyqWDVruSTeYHoc6NL8owCgYIKoZIzj0E 117 | AwIDRwAwRAIgakIxxTb+uxE+VCKHh5UXum8COyMsuCagkm3oT0Ahvu8CIGDdSJmh 118 | KUWDfdoihW+IkVFjntfdknCNVsQ0Awlm0No6 119 | -----END CERTIFICATE----- 120 | -------------------------------------------------------------------------------- /test/connprofiles/conn_profile_simple.yaml: -------------------------------------------------------------------------------- 1 | name: "simplest_with_discover" 2 | version: 1.0.0 3 | organizations: 4 | Org1: 5 | mspid: Org1MSP 6 | cryptoPath: ./msp 7 | peers: 8 | - peer0.org1.example.com 9 | 10 | peers: 11 | peer0.org1.example.com: 12 | url: peer0.org1.example.com:7051 13 | eventUrl: peer0.org1.example.com:7053 14 | grpcOptions: 15 | ssl-target-name-override: peer0.org1.example.com 16 | tlsCACerts: 17 | pem: | 18 | -----BEGIN CERTIFICATE----- 19 | MIICVzCCAf6gAwIBAgIRAMv2GfiYQXx7WfknTWg0ZBwwCgYIKoZIzj0EAwIwdjEL 20 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 21 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs 22 | c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjIxMTAwNzAwWhcNMzAwMjE4MTAw 23 | NzAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE 24 | BxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G 25 | A1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 26 | AwEHA0IABL7G3+3hAsTyDwlqwt+DjByElGH4wbfDPBdgvnBR4NuF5gk0KzEgHhLz 27 | /Z1nTxyM0TcQuuGqssMEHE+MYvBnyFCjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV 28 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV 29 | HQ4EIgQghTYOpq3gnj0xvs8871nl6SEvyqWDVruSTeYHoc6NL8owCgYIKoZIzj0E 30 | AwIDRwAwRAIgakIxxTb+uxE+VCKHh5UXum8COyMsuCagkm3oT0Ahvu8CIGDdSJmh 31 | KUWDfdoihW+IkVFjntfdknCNVsQ0Awlm0No6 32 | -----END CERTIFICATE----- 33 | 34 | -------------------------------------------------------------------------------- /test/connprofiles/conn_profile_simple_12.yaml: -------------------------------------------------------------------------------- 1 | name: "simplest_with_discover" 2 | version: 1.0.0 3 | organizations: 4 | Org1: 5 | mspid: Org1MSP 6 | cryptoPath: ./msp1 7 | peers: 8 | - peer0.org1.example.com 9 | Org2: 10 | mspid: Org2MSP 11 | cryptoPath: ./msp2 12 | peers: 13 | - peer0.org2.example.com 14 | peers: 15 | peer0.org1.example.com: 16 | url: peer0.org1.example.com:7051 17 | eventUrl: peer0.org1.example.com:7053 18 | grpcOptions: 19 | ssl-target-name-override: peer0.org1.example.com 20 | tlsCACerts: 21 | pem: | 22 | -----BEGIN CERTIFICATE----- 23 | MIICVzCCAf6gAwIBAgIRAMv2GfiYQXx7WfknTWg0ZBwwCgYIKoZIzj0EAwIwdjEL 24 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 25 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs 26 | c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjIxMTAwNzAwWhcNMzAwMjE4MTAw 27 | NzAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE 28 | BxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G 29 | A1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 30 | AwEHA0IABL7G3+3hAsTyDwlqwt+DjByElGH4wbfDPBdgvnBR4NuF5gk0KzEgHhLz 31 | /Z1nTxyM0TcQuuGqssMEHE+MYvBnyFCjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV 32 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV 33 | HQ4EIgQghTYOpq3gnj0xvs8871nl6SEvyqWDVruSTeYHoc6NL8owCgYIKoZIzj0E 34 | AwIDRwAwRAIgakIxxTb+uxE+VCKHh5UXum8COyMsuCagkm3oT0Ahvu8CIGDdSJmh 35 | KUWDfdoihW+IkVFjntfdknCNVsQ0Awlm0No6 36 | -----END CERTIFICATE----- 37 | peer0.org2.example.com: 38 | url: peer0.org2.example.com:9051 39 | eventUrl: peer0.org2.example.com:9053 40 | grpcOptions: 41 | ssl-target-name-override: peer0.org2.example.com 42 | tlsCACerts: 43 | pem: | 44 | -----BEGIN CERTIFICATE----- 45 | MIICVzCCAf6gAwIBAgIRAMv2GfiYQXx7WfknTWg0ZBwwCgYIKoZIzj0EAwIwdjEL 46 | MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG 47 | cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs 48 | c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjIxMTAwNzAwWhcNMzAwMjE4MTAw 49 | NzAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE 50 | BxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G 51 | A1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 52 | AwEHA0IABL7G3+3hAsTyDwlqwt+DjByElGH4wbfDPBdgvnBR4NuF5gk0KzEgHhLz 53 | /Z1nTxyM0TcQuuGqssMEHE+MYvBnyFCjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV 54 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV 55 | HQ4EIgQghTYOpq3gnj0xvs8871nl6SEvyqWDVruSTeYHoc6NL8owCgYIKoZIzj0E 56 | AwIDRwAwRAIgakIxxTb+uxE+VCKHh5UXum8COyMsuCagkm3oT0Ahvu8CIGDdSJmh 57 | KUWDfdoihW+IkVFjntfdknCNVsQ0Awlm0No6 58 | -----END CERTIFICATE----- 59 | -------------------------------------------------------------------------------- /test/connprofiles/conn_profile_simple_2.yaml: -------------------------------------------------------------------------------- 1 | name: "simplest_with_discover" 2 | version: 1.0.0 3 | organizations: 4 | Org1: 5 | mspid: Org2MSP 6 | cryptoPath: ./msp 7 | peers: 8 | - peer0.org2.example.com 9 | 10 | peers: 11 | peer0.org2.example.com: 12 | url: peer0.org2.example.com:9051 13 | eventUrl: peer0.org2.example.com:9053 14 | grpcOptions: 15 | ssl-target-name-override: peer0.org2.example.com 16 | tlsCACerts: 17 | pem: | 18 | -----BEGIN CERTIFICATE----- 19 | MIICVzCCAf2gAwIBAgIQRlROnf9rQUsOW774bTjaCzAKBggqhkjOPQQDAjB2MQsw 20 | CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy 21 | YW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz 22 | Y2Eub3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMjExMDA3MDBaFw0zMDAyMTgxMDA3 23 | MDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH 24 | Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMR8wHQYD 25 | VQQDExZ0bHNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D 26 | AQcDQgAEIIeq4aqugvMP+jAFKY524LdoXbZjCTNNmASqux2VvveQS8pLfPRO3BUu 27 | fiVFE4Jo2GoXG8149khvqZg2XJcc/6NtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud 28 | JQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud 29 | DgQiBCBbAJ5eVQai/afJZvJyifQaS0y2Zsrkxq81USBMaMp8fTAKBggqhkjOPQQD 30 | AgNIADBFAiEAztiqpstdAHlwiOsnGddhZ5SVh/mBgyTnQmq0HYd+ADMCIHtacZEm 31 | L49WVD6jHQlCKvFt59aEdzqsBxNeAFW7AGUk 32 | -----END CERTIFICATE----- 33 | 34 | -------------------------------------------------------------------------------- /util/network.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | type EndPointStatus int 10 | 11 | const ( 12 | EndPointStatus_Valid EndPointStatus = iota 13 | EndPointStatus_Connectable 14 | EndPointStatus_Refused 15 | EndPointStatus_Timeout 16 | EndPointStatus_NotFound 17 | ) 18 | 19 | func resolveAddress(address string) error { 20 | _, err := net.ResolveTCPAddr("tcp", address) 21 | return err 22 | } 23 | 24 | func connectAddress(address string) error { 25 | conn, err := net.DialTimeout("tcp", address, time.Second*3) 26 | if err != nil { 27 | return err 28 | } 29 | defer conn.Close() 30 | return nil 31 | } 32 | 33 | // GetEndpointStatus to get status of the peer endpoint. 34 | // The result must not be EndpointStatus_Valid, since that is determined by peer query, instead of this network method, 35 | func GetEndpointStatus(address string) EndPointStatus { 36 | _, err := net.ResolveTCPAddr("tcp", address) 37 | if err != nil { 38 | return EndPointStatus_NotFound 39 | } 40 | conn, err := net.DialTimeout("tcp", address, time.Second*2) 41 | if err != nil { 42 | msg := err.Error() 43 | if strings.Contains(msg, "connection refused") { 44 | return EndPointStatus_Refused 45 | } 46 | // What might includes "i/o timeout" 47 | return EndPointStatus_Timeout 48 | } 49 | defer conn.Close() 50 | return EndPointStatus_Connectable 51 | } 52 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "fmt" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | const ( 14 | PackageFormat_TARGZ = "tar.gz" 15 | PackageFormat_TAR = "tar" 16 | ) 17 | 18 | func UnTar(source []byte, packageFormat string, target string) error { 19 | var sourceReader io.Reader = bytes.NewReader(source) 20 | var err error 21 | if packageFormat == PackageFormat_TARGZ { 22 | sourceReader, err = gzip.NewReader(sourceReader) 23 | if err != nil { 24 | return err 25 | } 26 | } 27 | 28 | tarReader := tar.NewReader(sourceReader) 29 | for { 30 | hdr, err := tarReader.Next() 31 | if err == io.EOF { 32 | break 33 | } 34 | if err != nil { 35 | return err 36 | } 37 | info := hdr.FileInfo() 38 | 39 | if info.IsDir() { 40 | if err = os.MkdirAll(filepath.Join(target, hdr.Name), info.Mode()); err != nil { 41 | return err 42 | } 43 | } else { 44 | if err = copyFile(tarReader, filepath.Join(target, hdr.Name), info.Mode()); err != nil { 45 | return err 46 | } 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func copyFile(tarReader *tar.Reader, targetFilePath string, mode os.FileMode) error { 53 | targetFile, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) 54 | defer targetFile.Close() 55 | if err != nil { 56 | return err 57 | } 58 | _, err = io.Copy(targetFile, tarReader) 59 | return err 60 | } 61 | 62 | // ExistString to find if exist string 63 | func ExistString(strs []string, str string) bool { 64 | for _, tmp := range strs { 65 | if tmp == str { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | // Set an easy Set 73 | // TODO performance 74 | // TODO change interface{} key to string, for json.Marshal issue. 75 | // Not routine safe 76 | type Set []interface{} 77 | 78 | // NewSet create a set 79 | func NewSet(ks ...interface{}) Set { 80 | s := Set{} 81 | for _, k := range ks { 82 | s.Add(k) 83 | } 84 | return s 85 | } 86 | 87 | // NewStringSet create a set 88 | func NewStringSet(ks ...string) Set { 89 | s := Set{} 90 | for _, k := range ks { 91 | s.Add(k) 92 | } 93 | return s 94 | } 95 | 96 | // Add add key 97 | func (s *Set) Add(nv interface{}) { 98 | if !s.Exist(nv) { 99 | *s = append(*s, nv) 100 | } 101 | } 102 | 103 | // Exist if nexist key 104 | func (s *Set) Exist(nv interface{}) bool { 105 | for _, v := range *s { 106 | if v == nv { 107 | return true 108 | } 109 | } 110 | return false 111 | } 112 | 113 | // StringList get list as strings 114 | // TODO performance 115 | func (s *Set) StringList() []string { 116 | l := []string{} 117 | for _, v := range *s { 118 | l = append(l, fmt.Sprintf("%v", v)) 119 | } 120 | return l 121 | } 122 | -------------------------------------------------------------------------------- /web/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/.git/objects/**": true, 4 | "**/.git/subtree-cache/**": true, 5 | "**/node_modules/*/**": true 6 | } 7 | } -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Available Scripts 3 | 4 | ### `npm install -g yarn` 5 | 6 | ### `yarn` 7 | ### `yarn start` 8 | 9 | 10 | ### `yarn test` 11 | 12 | ### `yarn build` 13 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fablet-web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.8.2", 7 | "@material-ui/icons": "^4.5.1", 8 | "react": "^16.12.0", 9 | "react-dom": "^16.12.0", 10 | "react-router-dom": "^5.1.2", 11 | "react-scripts": "^3.3.0", 12 | "typeface-roboto": "^0.0.75", 13 | "request-promise-native": "^1.0.8", 14 | "websocket": "^1.0.31", 15 | "echarts": "^4.6.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/public/fablet.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/web/public/fablet.xcf -------------------------------------------------------------------------------- /web/public/fablet192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/web/public/fablet192.png -------------------------------------------------------------------------------- /web/public/fablet512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/web/public/fablet512.png -------------------------------------------------------------------------------- /web/public/fablet64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/web/public/fablet64.png -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fablet/1bc764d5babc564f2e33ecbfedad1074535978c4/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Fablet 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fablet", 3 | "name": "Fablet - client for Hyperledger Fabric", 4 | "icons": [ 5 | { 6 | "src": "fablet64.png", 7 | "sizes": "64x64", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "fablet192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "fablet512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /web/src/common/constants.js: -------------------------------------------------------------------------------- 1 | import * as ENV from "./env"; 2 | 3 | export const __VER_0_1_0__ = "0.1.0"; 4 | export const __CODE_MERCURY__ = "mercury"; 5 | 6 | export const __VER__ = "ver"; 7 | export const __CODE__ = "code"; 8 | 9 | export const __LOC_EN__ = "en"; 10 | export const __LOC_CN__ = "cn"; 11 | 12 | export const __DATA_VER__ = "data_version"; 13 | 14 | export const getCurrVer = () => { 15 | return { 16 | __VER__: __VER_0_1_0__, 17 | __CODE__: __CODE_MERCURY__ 18 | } 19 | } 20 | 21 | 22 | export const getServiceURL = (path) => { 23 | // For dev service 24 | if (ENV.DEBUG) { 25 | return ENV.getDebugServiceURL(path); 26 | } 27 | 28 | // For prod 29 | let port = document.location.port; 30 | if (port !== "") { 31 | port = ":" + port; 32 | } 33 | return `${document.location.protocol}//${document.location.hostname}${port}${path}`; 34 | } 35 | 36 | export const getWebsocketURL = (path) => { 37 | // For dev service 38 | if (ENV.DEBUG) { 39 | return ENV.getDebugWebsocketURL(path); 40 | } 41 | 42 | // For prod 43 | const wsProtocol = document.location.protocol.startsWith("https") ? "wss:" : "ws:"; 44 | let port = document.location.port; 45 | if (port !== "") { 46 | port = ":" + port; 47 | } 48 | return `${wsProtocol}//${document.location.hostname}${port}${path}`; 49 | } 50 | 51 | export const TIMEOUT_NETWORKOVERVIEW_UPDATE = 300000; 52 | export const TIMEOUT_PEERDETAILS_UPDATE = 300000; 53 | export const TIMEOUT_LEDGER_UPDATE = 3000; 54 | 55 | export const RES_OK = 200; 56 | 57 | export const INITIAL_BLOCKS = 64; 58 | 59 | export const CHAINCODE_TYPE_GOLANG = "golang"; 60 | export const CHAINCODE_TYPE_NODE = "node"; 61 | export const CHAINCODE_TYPE_JAVA = "java"; 62 | export const CHAINCODE_TYPES = [CHAINCODE_TYPE_GOLANG, CHAINCODE_TYPE_NODE, CHAINCODE_TYPE_JAVA]; -------------------------------------------------------------------------------- /web/src/common/debugflag.js: -------------------------------------------------------------------------------- 1 | export const DEBUG = true; -------------------------------------------------------------------------------- /web/src/common/env.js: -------------------------------------------------------------------------------- 1 | import * as debugFlag from "./debugflag"; 2 | 3 | // TODO To use build arguments. 4 | export const DEBUG = debugFlag.DEBUG; 5 | 6 | export const getDebugServiceURL = (path) => `http://${document.location.hostname}:8080${path}`; 7 | export const getDebugWebsocketURL = (path) => `ws://${document.location.hostname}:8080${path}`; -------------------------------------------------------------------------------- /web/src/common/log.js: -------------------------------------------------------------------------------- 1 | 2 | const log = { 3 | info: console.info, 4 | error: console.error, 5 | log: console.log, 6 | debug: console.debug 7 | } 8 | 9 | export { log }; -------------------------------------------------------------------------------- /web/src/common/utils.js: -------------------------------------------------------------------------------- 1 | import { clientStorage } from "../data/localstore"; 2 | import i18n from '../i18n'; 3 | import { log } from "./log"; 4 | import requestPromise from "request-promise-native"; 5 | import * as CONST from "./constants"; 6 | 7 | const cntTrim = (str, len) => { 8 | if (!str) { 9 | return str; 10 | } 11 | return str.length > len ? (str.substring(0, len) + "...") : str; 12 | }; 13 | // TODO to use common id/conn object. 14 | const getCurrIDConn = () => { 15 | const currID = clientStorage.getCurrID(); 16 | const currProfile = clientStorage.getCurrProfile() 17 | const currIDConn = { 18 | label: currID.label, 19 | MSPID: currID.MSPID, 20 | certContent: currID.certContent, 21 | prvKeyContent: currID.prvKeyContent, 22 | connProfile: currProfile.content 23 | }; 24 | 25 | return !currIDConn.MSPID || !currIDConn.certContent || !currIDConn.prvKeyContent || !currIDConn.connProfile 26 | ? {} : currIDConn; 27 | }; 28 | 29 | const outputServiceErrorResult = (result) => { 30 | return i18n("res_code") + ": " + result.resCode + ". " + i18n("err_message") + ": " + result.errMsg; 31 | }; 32 | 33 | const formatTime = (ts) => { 34 | const t = new Date(ts); 35 | return t.getFullYear() + "-" + fillOf2((t.getMonth()+1)) + "-" + fillOf2(t.getDate()) + " " 36 | + fillOf2(t.getHours()) + ":" + fillOf2(t.getMinutes()) + ":" + fillOf2(t.getSeconds()) + "." + t.getMilliseconds(); 37 | }; 38 | 39 | const fillOf2 = (v) => { 40 | return v < 10 ? '0' + v : v; 41 | } 42 | 43 | 44 | const refreshNetwork = async () => { 45 | const currIDConn = getCurrIDConn(); 46 | const reqBody = { 47 | connection: currIDConn 48 | }; 49 | 50 | let myHeaders = new Headers({ 51 | 'Accept': 'application/json', 52 | 'Content-Type': 'application/json' 53 | }); 54 | 55 | let option = { 56 | url: CONST.getServiceURL("/network/refresh"), 57 | method: 'POST', 58 | headers: myHeaders, 59 | json: true, 60 | resolveWithFullResponse: true, 61 | body: reqBody 62 | }; 63 | 64 | log.debug("Refresh network."); 65 | 66 | let result = await requestPromise(option); 67 | 68 | if (result) { 69 | result = result.body; 70 | if (result.resCode === 200) { 71 | window.location.reload(); 72 | } 73 | else { 74 | // this.setState({ executeError: result.resCode + ". " + result.errMsg }); 75 | log.error("Error: ", result.resCode, result.errMsg); 76 | } 77 | } 78 | else { 79 | // this.setState({ executeError: i18n("no_result_error") }); 80 | log.error("No result returned.") 81 | } 82 | } 83 | 84 | 85 | export { cntTrim, getCurrIDConn, outputServiceErrorResult, formatTime, refreshNetwork }; -------------------------------------------------------------------------------- /web/src/components/channel/create.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dialog from '@material-ui/core/Dialog'; 3 | import DialogActions from '@material-ui/core/DialogActions'; 4 | import DialogContent from '@material-ui/core/DialogContent'; 5 | import DialogTitle from '@material-ui/core/DialogTitle'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | import Button from '@material-ui/core/Button'; 9 | import TextField from '@material-ui/core/TextField'; 10 | import Grid from '@material-ui/core/Grid'; 11 | import Box from '@material-ui/core/Box'; 12 | import Fade from '@material-ui/core/Fade'; 13 | import CircularProgress from '@material-ui/core/CircularProgress'; 14 | import { getCurrIDConn } from "../../common/utils"; 15 | import { log } from "../../common/log"; 16 | import i18n from '../../i18n'; 17 | import * as CONST from "../../common/constants"; 18 | 19 | const styles = (theme) => ({ 20 | formField: { 21 | fontSize: 13, 22 | }, 23 | }); 24 | 25 | class ChannelCreate extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | this.state = { 29 | ...props, 30 | // callBack: props.callBack, 31 | // onClose 32 | // channelCreateOption 33 | 34 | createError: "", 35 | loading: false, 36 | }; 37 | 38 | this.tempForm = {}; 39 | 40 | this.handleCancel = this.handleCancel.bind(this); 41 | this.handleSubmit = this.handleSubmit.bind(this); 42 | this.handleCreated = this.handleCreated.bind(this); 43 | this.onSelectFile = this.onSelectFile.bind(this); 44 | log.debug("ChannelCreate: constructor"); 45 | } 46 | 47 | componentDidMount() { 48 | } 49 | 50 | componentDidUpdate() { 51 | } 52 | 53 | componentWillUnmount() { 54 | } 55 | 56 | handleCancel() { 57 | this.setState({ open: false }); 58 | this.state.onClose(); 59 | } 60 | 61 | // TODO change to handleInstalled 62 | handleCreated() { 63 | this.setState({ open: false }); 64 | this.state.onClose(); 65 | // Callback from parent, to update the peer overview. 66 | this.state.callBack(); 67 | } 68 | 69 | onSelectFile(event) { 70 | // TODO To support Safari 71 | const fileReader = new FileReader(); 72 | const tempForm = this.tempForm; 73 | const fieldName = event.target.name; 74 | const f = event.target.files[0]; 75 | if (!f) { 76 | return; 77 | } 78 | 79 | this.setState({ txContentFile: f.name }); 80 | 81 | fileReader.readAsBinaryString(f); 82 | fileReader.onload = (e) => { 83 | tempForm[fieldName] = { 84 | path: f.name, 85 | // Encode the binary content to bse64. 86 | content: btoa(e.target.result) 87 | }; 88 | 89 | log.debug("Selected file", tempForm[fieldName].path, tempForm[fieldName].content.length); 90 | }; 91 | } 92 | 93 | handleSubmit(event) { 94 | event.preventDefault(); 95 | 96 | this.setState({ 97 | loading: true, 98 | createError: "" 99 | }); 100 | 101 | // To check id/conn validity. 102 | const currIDConn = getCurrIDConn(); 103 | const formData = new FormData(event.target); 104 | const reqBody = { 105 | connection: currIDConn, 106 | txContent: this.tempForm["txContent"].content, 107 | orderer: formData.get("orderer"), 108 | }; 109 | 110 | let myHeaders = new Headers({ 111 | 'Accept': 'application/json', 112 | 'Content-Type': 'application/octet-stream' 113 | }); 114 | let request = new Request(CONST.getServiceURL("/channel/create"), { 115 | method: 'POST', 116 | mode: 'cors', 117 | //credentials: 'include', 118 | headers: myHeaders, 119 | body: JSON.stringify(reqBody), 120 | }); 121 | 122 | fetch(request) 123 | .then(response => response.json()) 124 | .then(result => { 125 | log.debug("ChannelCreate result", result); 126 | 127 | this.setState({ 128 | loading: false 129 | }); 130 | 131 | if (result) { 132 | if (result.resCode === CONST.RES_OK) { 133 | this.handleCreated(); 134 | } 135 | else { 136 | this.setState({ createError: i18n("res_code") + " " + result.resCode + ". " + result.errMsg }); 137 | } 138 | } 139 | }) 140 | .catch(function (err) { 141 | log.error("Exception in creating:", err); 142 | this.setState({ 143 | createError: String(err), 144 | loading: false 145 | }); 146 | }); 147 | } 148 | 149 | render() { 150 | if (!this.state.open) { 151 | return null; 152 | } 153 | 154 | const classes = this.props.classes; 155 | 156 | return ( 157 | 165 | {i18n("channel_create")} 166 |
167 | 168 | 169 | 170 | 171 | 180 | 185 | 186 | 187 | {this.state.txContentFile} 188 | 189 | 190 | 191 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 220 | 221 | 222 | 223 | 224 | 225 | 234 |      235 | 242 | 243 | 244 | 245 | 246 | 247 | { 248 | this.state.createError ? ( 249 | 250 | 251 | {this.state.createError} 252 | 253 | 254 | ) : null 255 | } 256 | 257 |
258 |
259 | ); 260 | } 261 | } 262 | 263 | export default withStyles(styles)(ChannelCreate); -------------------------------------------------------------------------------- /web/src/components/channel/join.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dialog from '@material-ui/core/Dialog'; 3 | import DialogActions from '@material-ui/core/DialogActions'; 4 | import DialogContent from '@material-ui/core/DialogContent'; 5 | import DialogTitle from '@material-ui/core/DialogTitle'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | import Button from '@material-ui/core/Button'; 9 | import TextField from '@material-ui/core/TextField'; 10 | import Grid from '@material-ui/core/Grid'; 11 | import Box from '@material-ui/core/Box'; 12 | import Fade from '@material-ui/core/Fade'; 13 | import CircularProgress from '@material-ui/core/CircularProgress'; 14 | import { getCurrIDConn } from "../../common/utils"; 15 | import { log } from "../../common/log"; 16 | import i18n from '../../i18n'; 17 | import * as CONST from "../../common/constants"; 18 | 19 | const styles = (theme) => ({ 20 | formField: { 21 | fontSize: 13, 22 | }, 23 | }); 24 | 25 | class ChannelJoin extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | this.state = { 29 | ...props, 30 | // callBack: props.callBack, 31 | // onClose 32 | // channelJoinOption 33 | // peer 34 | 35 | joinError: "", 36 | loading: false, 37 | }; 38 | 39 | this.handleCancel = this.handleCancel.bind(this); 40 | this.handleSubmit = this.handleSubmit.bind(this); 41 | this.handleJoined = this.handleJoined.bind(this); 42 | log.debug("ChannelJoin: constructor"); 43 | } 44 | 45 | componentDidMount() { 46 | } 47 | 48 | componentDidUpdate() { 49 | } 50 | 51 | componentWillUnmount() { 52 | } 53 | 54 | handleCancel() { 55 | this.setState({ open: false }); 56 | this.state.onClose(); 57 | } 58 | 59 | // TODO change to handleInstalled 60 | handleJoined() { 61 | this.setState({ open: false }); 62 | this.state.onClose(); 63 | // Callback from parent, to update the peer overview. 64 | this.state.callBack(); 65 | } 66 | 67 | handleSubmit(event) { 68 | event.preventDefault(); 69 | 70 | this.setState({ 71 | loading: true, 72 | joinError: "" 73 | }); 74 | 75 | // To check id/conn validity. 76 | const currIDConn = getCurrIDConn(); 77 | const formData = new FormData(event.target); 78 | const reqBody = { 79 | connection: currIDConn, 80 | channelID: formData.get("channelID"), 81 | targets: [this.state.channelJoinOption.peer.name], 82 | orderer: formData.get("orderer"), 83 | }; 84 | 85 | let myHeaders = new Headers({ 86 | 'Accept': 'application/json', 87 | 'Content-Type': 'application/octet-stream' 88 | }); 89 | let request = new Request(CONST.getServiceURL("/channel/join"), { 90 | method: 'POST', 91 | mode: 'cors', 92 | //credentials: 'include', 93 | headers: myHeaders, 94 | body: JSON.stringify(reqBody), 95 | }); 96 | 97 | fetch(request) 98 | .then(response => response.json()) 99 | .then(result => { 100 | log.debug("ChannelJoin result", result); 101 | 102 | this.setState({ 103 | loading: false 104 | }); 105 | 106 | if (result) { 107 | if (result.resCode === CONST.RES_OK) { 108 | this.handleJoined(); 109 | } 110 | else { 111 | this.setState({ joinError: i18n("res_code") + result.resCode + ". " + result.errMsg }); 112 | } 113 | } 114 | }) 115 | .catch(function (err) { 116 | log.error("Exception in joining:", err); 117 | this.setState({ 118 | joinError: String(err), 119 | loading: false 120 | }); 121 | }); 122 | } 123 | 124 | render() { 125 | if (!this.state.open) { 126 | return null; 127 | } 128 | 129 | const classes = this.props.classes; 130 | 131 | return ( 132 | 140 | {i18n("channel_join")} 141 |
142 | 143 | 144 | 145 | 146 | 159 | 160 | 161 | 162 | 173 | 174 | 175 | 176 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 205 | 206 | 207 | 208 | 209 | 210 | 219 |   220 | 227 | 228 | 229 | 230 | 231 | 232 | { 233 | this.state.joinError ? ( 234 | 235 | 236 | {this.state.joinError} 237 | 238 | 239 | ) : null 240 | } 241 | 242 |
243 |
244 | ); 245 | } 246 | } 247 | 248 | export default withStyles(styles)(ChannelJoin); -------------------------------------------------------------------------------- /web/src/components/ledger/blockchart.js: -------------------------------------------------------------------------------- 1 | import i18n from '../../i18n'; 2 | const echarts = require("echarts/lib/echarts"); 3 | require('echarts/lib/chart/bar'); 4 | require('echarts/lib/component/tooltip'); 5 | require('echarts/lib/component/title'); 6 | 7 | class BlockEventChart { 8 | constructor(elementID) { 9 | this.elementID = elementID; 10 | this.chart = echarts.init(document.getElementById(this.elementID)); 11 | 12 | this.style = { 13 | grid_lit: { grid: { top: 0, left: 0, right: 0, bottom: 0 } }, 14 | grid_normal: { grid: { top: 50, left: 50, right: 0, bottom: 20} }, 15 | title_lit: { title: { show: false } }, 16 | title_normal: { title: { show: true, text: i18n("block_event_chart_tx"), left: "center" } } 17 | }; 18 | } 19 | 20 | initChart() { 21 | this.chart.setOption({ 22 | ...this.style.grid_lit, 23 | ...this.style.title_lit, 24 | animation: false, 25 | xAxis: { type: "time", show: true, name: i18n("block_time"), nameLocation: "end" }, 26 | yAxis: { show: true , name: i18n("block_quantity") }, 27 | series: [ 28 | { 29 | type: 'bar', 30 | data: [] 31 | } 32 | ] 33 | }); 34 | } 35 | 36 | showChart(data) { 37 | this.chart.setOption({ 38 | series: [{ 39 | data: data 40 | }] 41 | }); 42 | } 43 | 44 | resize(fullChart) { 45 | this.chart.setOption({ 46 | ...(fullChart ? this.style.title_normal : this.style.title_lit), 47 | ...(fullChart ? this.style.grid_normal : this.style.grid_lit) 48 | }); 49 | this.chart.resize(); 50 | } 51 | } 52 | 53 | export default BlockEventChart; -------------------------------------------------------------------------------- /web/src/components/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { withStyles } from '@material-ui/core/styles'; 4 | import CssBaseline from '@material-ui/core/CssBaseline'; 5 | import Drawer from '@material-ui/core/Drawer'; 6 | import AppBar from '@material-ui/core/AppBar'; 7 | import Toolbar from '@material-ui/core/Toolbar'; 8 | import List from '@material-ui/core/List'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Divider from '@material-ui/core/Divider'; 11 | import IconButton from '@material-ui/core/IconButton'; 12 | import Container from '@material-ui/core/Container'; 13 | import MenuIcon from '@material-ui/icons/Menu'; 14 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; 15 | import { mainListItems } from './menu/list_items'; 16 | import ConnectionMenu from "./menu/connection"; 17 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 18 | 19 | import { clientStorage } from "../data/localstore"; 20 | import Profile from './network/profile'; 21 | import Discover from "./network/discover"; 22 | import { log } from "../common/log"; 23 | 24 | function Copyright() { 25 | return ( 26 | 27 | {'Copyright © '} 28 | Fablet 29 | {' '} 30 | {new Date().getFullYear()} 31 | {'.'} 32 | 33 | ); 34 | } 35 | 36 | const drawerWidth = 240; 37 | 38 | const styles = (theme) => ({ 39 | 40 | root: { 41 | display: 'flex' 42 | }, 43 | toolbar: { 44 | paddingRight: 24, // keep right padding when drawer closed 45 | }, 46 | toolbarIcon: { 47 | display: 'flex', 48 | alignItems: 'center', 49 | justifyContent: 'flex-end', 50 | padding: '0 8px', 51 | ...theme.mixins.toolbar, 52 | }, 53 | appBar: { 54 | zIndex: theme.zIndex.drawer + 1, 55 | transition: theme.transitions.create(['width', 'margin'], { 56 | easing: theme.transitions.easing.sharp, 57 | duration: theme.transitions.duration.leavingScreen, 58 | }), 59 | }, 60 | appBarShift: { 61 | marginLeft: drawerWidth, 62 | width: `calc(100% - ${drawerWidth}px)`, 63 | transition: theme.transitions.create(['width', 'margin'], { 64 | easing: theme.transitions.easing.sharp, 65 | duration: theme.transitions.duration.enteringScreen, 66 | }), 67 | }, 68 | menuButton: { 69 | marginRight: 36, 70 | }, 71 | menuButtonHidden: { 72 | display: 'none', 73 | }, 74 | title: { 75 | flexGrow: 1, 76 | }, 77 | drawerPaper: { 78 | position: 'relative', 79 | whiteSpace: 'nowrap', 80 | width: drawerWidth, 81 | transition: theme.transitions.create('width', { 82 | easing: theme.transitions.easing.sharp, 83 | duration: theme.transitions.duration.enteringScreen, 84 | }), 85 | }, 86 | drawerPaperClose: { 87 | overflowX: 'hidden', 88 | transition: theme.transitions.create('width', { 89 | easing: theme.transitions.easing.sharp, 90 | duration: theme.transitions.duration.leavingScreen, 91 | }), 92 | width: theme.spacing(7), 93 | [theme.breakpoints.up('sm')]: { 94 | width: theme.spacing(9), 95 | }, 96 | }, 97 | appBarSpacer: theme.mixins.toolbar, 98 | content: { 99 | flexGrow: 1, 100 | height: '100vh', 101 | overflow: 'auto' 102 | }, 103 | container: { 104 | paddingTop: theme.spacing(4), 105 | paddingBottom: theme.spacing(4), 106 | }, 107 | paper: { 108 | padding: theme.spacing(2), 109 | display: 'flex', 110 | overflow: 'auto', 111 | flexDirection: 'column', 112 | }, 113 | fixedHeight: { 114 | height: 240, 115 | }, 116 | }); 117 | 118 | 119 | class Main extends React.Component { 120 | constructor(props) { 121 | super(props); 122 | this.state = { 123 | ...props, 124 | leftMenuOpen: true, 125 | profileOpen: false, 126 | }; 127 | 128 | this.handleLeftMenuOpen = this.handleLeftMenuOpen.bind(this); 129 | this.handleLeftMenuClose = this.handleLeftMenuClose.bind(this); 130 | this.handleProfileOpen = this.handleProfileOpen.bind(this); 131 | this.handleProfileClose = this.handleProfileClose.bind(this); 132 | 133 | log.debug("Main: constructor"); 134 | } 135 | 136 | componentDidMount() { 137 | const needConnProf = clientStorage.loadConnectionProfiles().length <= 0; 138 | const needIdentity = clientStorage.loadIdentities().length <= 0; 139 | 140 | this.setState({ 141 | profileOpen: needConnProf || needIdentity, 142 | needConnProf: needConnProf, 143 | needIdentity: needIdentity 144 | }); 145 | } 146 | 147 | componentDidUpdate() { 148 | } 149 | 150 | componentWillUnmount() { 151 | } 152 | 153 | handleLeftMenuOpen() { 154 | this.setState({ leftMenuOpen: true }); 155 | } 156 | 157 | handleLeftMenuClose() { 158 | this.setState({ leftMenuOpen: false }); 159 | } 160 | 161 | handleProfileOpen(needConnProf, needIdentity) { 162 | const main = this; 163 | // TODO UI refresh performance issues. 164 | // log.info(">>>>>>>>>>>>>>>>>>>>>> return the function self with ", needConnProf, needIdentity); 165 | return function () { 166 | // log.info("####################### execute the function self with ", needConnProf, needIdentity); 167 | main.setState({ 168 | profileOpen: true, 169 | needConnProf: needConnProf, 170 | needIdentity: needIdentity 171 | }); 172 | } 173 | } 174 | 175 | handleProfileClose() { 176 | this.setState({ profileOpen: false }); 177 | } 178 | 179 | render() { 180 | const classes = this.props.classes; 181 | 182 | const connProfs = clientStorage.loadConnectionProfiles(); 183 | const identities = clientStorage.loadIdentities(); 184 | 185 | return ( 186 |
187 | 188 | 189 | 190 | 197 | 198 | 199 | 200 | Fablet 201 | 202 | {/* 203 | 204 | 205 | 206 | */} 207 | 208 | 209 | 210 | 211 | 212 | 219 |
220 | 221 | 222 | 223 |
224 | 225 | 226 | {mainListItems} 227 | 228 | 229 | 230 | 233 | 234 |
235 | 236 |
237 |
238 | 239 | { 240 | connProfs.length > 0 && identities.length > 0 ? ( 241 | 242 | } /> 243 | {/* } /> */} 244 | ) 245 | : 246 |
247 | } 248 |
249 | 250 | 257 | 258 | 259 |
260 | 261 |
262 | 263 | 264 |
265 | ); 266 | } 267 | 268 | } 269 | 270 | export default withStyles(styles)(Main); -------------------------------------------------------------------------------- /web/src/components/menu/list_items.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ListItem from '@material-ui/core/ListItem'; 3 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 4 | import ListItemText from '@material-ui/core/ListItemText'; 5 | import DashboardIcon from '@material-ui/icons/Dashboard'; 6 | import { Link } from "react-router-dom"; 7 | import i18n from "../../i18n"; 8 | import "../../css/common.css"; 9 | import { Typography } from '@material-ui/core'; 10 | 11 | export const mainListItems = ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | {i18n("network")} 20 | 21 | 22 | 23 | 24 | {/* 25 | 26 | 27 | 28 | 29 | 30 | {i18n("channel")} 31 | 32 | 33 | */} 34 | 35 |
36 | ); 37 | -------------------------------------------------------------------------------- /web/src/components/network/discover.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import PeerOverview from './peer_overview'; 5 | import { withStyles } from '@material-ui/core/styles'; 6 | import { getCurrIDConn } from "../../common/utils"; 7 | import { log } from "../../common/log"; 8 | import Typography from '@material-ui/core/Typography'; 9 | import * as CONST from "../../common/constants"; 10 | 11 | const styles = theme => ({ 12 | root: { 13 | display: 'flex', 14 | }, 15 | toolbar: { 16 | paddingRight: 24, // keep right padding when drawer closed 17 | }, 18 | toolbarIcon: { 19 | display: 'flex', 20 | alignItems: 'center', 21 | justifyContent: 'flex-end', 22 | padding: '0 8px', 23 | ...theme.mixins.toolbar, 24 | }, 25 | appBar: { 26 | zIndex: theme.zIndex.drawer + 1, 27 | transition: theme.transitions.create(['width', 'margin'], { 28 | easing: theme.transitions.easing.sharp, 29 | duration: theme.transitions.duration.leavingScreen, 30 | }), 31 | }, 32 | appBarSpacer: { 33 | height: 20 34 | }, 35 | content: { 36 | flexGrow: 1, 37 | height: '80vh', 38 | overflow: 'auto', 39 | }, 40 | container: { 41 | paddingTop: theme.spacing(4), 42 | paddingBottom: theme.spacing(4), 43 | }, 44 | paper: { 45 | padding: theme.spacing(1), 46 | display: 'flex', 47 | overflow: 'auto', 48 | flexDirection: 'column', 49 | }, 50 | fixedHeight: { 51 | height: 180, 52 | }, 53 | }); 54 | 55 | 56 | /** 57 | * 58 | * @param {object} props: {peers: [peer, peer, ...]} 59 | */ 60 | class __DiscoverComp extends React.Component { 61 | constructor(props) { 62 | super(props); 63 | this.state = { ...props }; 64 | // peers, peerStatuses, channelLedgers, channelOrderers, channelChaincodes 65 | 66 | this.state.focusPeer = undefined; 67 | 68 | this.handleFocusPeer = this.handleFocusPeer.bind(this); 69 | 70 | log.debug("__DiscoverComp: constructor"); 71 | } 72 | 73 | componentDidMount() { 74 | } 75 | 76 | componentDidUpdate() { 77 | } 78 | 79 | componentWillUnmount() { 80 | } 81 | 82 | handleFocusPeer(peer) { 83 | const comp = this; 84 | return function () { 85 | log.log("Focus...", peer); 86 | comp.setState({ focusPeer: peer }); 87 | }; 88 | } 89 | 90 | render() { 91 | const classes = this.state.classes; 92 | // const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight); 93 | return ( 94 |
95 | {/* */} 96 | 97 |
98 | {/* */} 99 | 100 | { 101 | this.state.peers ? this.state.peers.map((peer) => { 102 | peer.isFocus = this.state.focusPeer && peer.URL === this.state.focusPeer.URL; 103 | peer.isShow = !this.state.focusPeer || peer.isFocus 104 | return peer; 105 | }).filter((peer) => { 106 | return peer.isShow; 107 | }).map((peer) => { 108 | return ( 109 | 110 | {/* */} 111 | 123 | {/* */} 124 | ); 125 | }) : null 126 | } 127 | 128 | {/* */} 129 | 130 | 131 |
132 | ); 133 | } 134 | 135 | } 136 | 137 | const DiscoverComp = withStyles(styles)(__DiscoverComp); 138 | 139 | class Discover extends React.Component { 140 | constructor(props) { 141 | super(props); 142 | this.state = { 143 | ...props, 144 | resCode: 200, 145 | peers: [], 146 | ledgers: {}, 147 | errMsg: "", 148 | 149 | }; 150 | 151 | this.isRunning = true; 152 | this.updateTO = undefined; 153 | 154 | this.discoverUpdate = this.discoverUpdate.bind(this); 155 | 156 | log.debug("Discover: constructor", this.state.isRunning); 157 | } 158 | 159 | discoverUpdate() { 160 | // Remove the current timeout to avoid any duplicate invoking. 161 | if (!this.isRunning) { 162 | return; 163 | } 164 | 165 | const currIDConn = getCurrIDConn(); 166 | 167 | const reqBody = { 168 | connection: currIDConn 169 | }; 170 | 171 | let myHeaders = new Headers({ 172 | 'Accept': 'application/json', 173 | 'Content-Type': 'application/json' 174 | }); 175 | let request = new Request(CONST.getServiceURL("/network/discover"), { 176 | method: 'POST', 177 | //mode: 'cors', 178 | //credentials: 'include', 179 | headers: myHeaders, 180 | body: JSON.stringify(reqBody), 181 | }); 182 | 183 | // TODO issue: it always running... although it is unmounted. 184 | // TODO no discover network if the peer is focused with only one 185 | log.debug("Discover network."); 186 | 187 | fetch(request) 188 | .then(response => response.json()) 189 | .then(result => { 190 | log.debug("Discover result", result); 191 | // result is with peers 192 | if (this.isRunning) { 193 | this.setState(result); 194 | // "peers": networkOverview.Peers, 195 | // "channelLedgers": networkOverview.ChannelLedgers, 196 | // "channelChaincodes": networkOverview.ChannelChainCodes, 197 | // "channelOrderers": networkOverview.ChannelOrderers, 198 | this.updateTO = setTimeout(this.discoverUpdate, CONST.TIMEOUT_NETWORKOVERVIEW_UPDATE); 199 | } 200 | }); 201 | } 202 | 203 | componentDidMount() { 204 | log.debug("Discover: componentDidMount", this.state.isRunning); 205 | this.discoverUpdate(); 206 | } 207 | 208 | componentDidUpdate() { 209 | } 210 | 211 | componentWillUnmount() { 212 | log.debug("Clear timeout task of the discover update"); 213 | //clearTimeout(this.updateTO); // To avoid memory leak. 214 | this.isRunning = false; 215 | clearTimeout(this.updateTO); 216 | } 217 | 218 | render() { 219 | if (this.state.resCode === 200) { 220 | return (); 228 | } 229 | else { 230 | return ( 231 | Error! 232 | {this.state.resCode} 233 | {this.state.errMsg} 234 | ); 235 | } 236 | } 237 | } 238 | 239 | export default Discover; 240 | -------------------------------------------------------------------------------- /web/src/css/common.css: -------------------------------------------------------------------------------- 1 | a:link, a:visited { 2 | text-decoration: none; 3 | color: #272747; 4 | } 5 | -------------------------------------------------------------------------------- /web/src/data/localstore.js: -------------------------------------------------------------------------------- 1 | import { log } from "../common/log"; 2 | 3 | const __KEY_CONN_PROFS__ = "conn_profs"; 4 | const __KEY_CURR_CONN_PROF__ = "curr_conn_prof"; 5 | const __KEY_IDENTITIES__ = "identities"; 6 | const __KEY_CURR_ID__ = "curr_id"; 7 | 8 | // TODO TO check if localstorage is supported 9 | // TODO to cache the connection profile and identity. 10 | class ConnectionProfile { 11 | constructor(options){ 12 | log.log("Construct ConnectionProfile"); 13 | this.name = options.name; 14 | this.path = options.path; 15 | this.content = options.content; 16 | } 17 | isValid() { 18 | return this.name && this.path && this.content; 19 | } 20 | } 21 | 22 | const parseJSON = (str) => { 23 | try { 24 | return JSON.parse(str); 25 | } 26 | catch (e) { 27 | return []; 28 | } 29 | } 30 | 31 | class Identity { 32 | constructor(options){ 33 | console.log("Construct ConnectionProfile"); 34 | this.label = options.label; 35 | this.MSPID = options.MSPID; 36 | this.certPath = options.certPath; 37 | this.certContent = options.certContent; 38 | this.prvKeyPath = options.prvKeyPath; 39 | this.prvKeyContent = options.prvKeyContent; 40 | } 41 | isValid() { 42 | return this.label && this.MSPID && this.certContent && this.prvKeyContent; 43 | } 44 | } 45 | 46 | class ClientStorage { 47 | constructor() { 48 | log.debug("Construct ClientStorage..............................................."); 49 | this.profiles = []; 50 | this.identities = []; 51 | this.currConnProfileName = ""; 52 | this.currIDLabel = ""; 53 | 54 | this.init(); 55 | } 56 | init() { 57 | log.log("Init connection profiles."); 58 | this.profiles = function() { 59 | const profiles = parseJSON(localStorage.getItem(__KEY_CONN_PROFS__)); 60 | if (profiles && profiles instanceof Array) { 61 | return profiles 62 | .map(p => new ConnectionProfile(p)) 63 | .filter(p => p.isValid()); 64 | } 65 | return []; 66 | }(); 67 | this.identities = function(){ 68 | const ids = parseJSON(localStorage.getItem(__KEY_IDENTITIES__)); 69 | if (ids && ids instanceof Array) { 70 | return ids 71 | .map(i => new Identity(i)) 72 | .filter(i => i.isValid()); 73 | } 74 | return []; 75 | }(); 76 | this.currIDLabel = localStorage.getItem(__KEY_CURR_ID__) || ""; 77 | this.currConnProfileName = localStorage.getItem(__KEY_CURR_CONN_PROF__) || ""; 78 | 79 | let needRefresh = false; 80 | if (!this.getIDByName(this.identities, this.currIDLabel)){ 81 | this.setCurrIDLabel(this.identities.length > 0 ? this.identities[0].label: "") 82 | needRefresh = this.identities.length > 0; 83 | } 84 | if (!this.getProfileByName(this.profiles, this.currConnProfileName)) { 85 | this.setCurrConnProfileName(this.profiles.length > 0 ? this.profiles[0].name : ""); 86 | needRefresh = needRefresh || this.profiles.length > 0; 87 | } 88 | if (needRefresh) { 89 | window.location.reload(); 90 | } 91 | } 92 | 93 | loadConnectionProfiles() { 94 | return this.profiles; 95 | } 96 | 97 | saveConnectionProfile(options) { 98 | const temp = new ConnectionProfile(options); 99 | if (!temp.isValid()) { 100 | return; 101 | } 102 | 103 | const origName = temp.name; 104 | const updatedConnProfs = this.loadConnectionProfiles(); 105 | 106 | // At most 1024 duplicates. 107 | for (let i=1; i<1025; i++) { 108 | if (this.getProfileByName(updatedConnProfs, temp.name)) { 109 | temp.name = origName + `(${i})`; 110 | } 111 | else { 112 | break; 113 | } 114 | } 115 | 116 | updatedConnProfs.push(temp); 117 | localStorage.setItem(__KEY_CONN_PROFS__, JSON.stringify(updatedConnProfs)); 118 | 119 | log.debug("saveConnectionProfile"); 120 | } 121 | 122 | setCurrConnProfileName (currName) { 123 | localStorage.setItem(__KEY_CURR_CONN_PROF__, currName); 124 | this.currConnProfileName = currName; 125 | } 126 | 127 | getCurrConnProfileName() { 128 | return this.currConnProfileName; 129 | } 130 | 131 | getCurrProfile() { 132 | return this.getProfileByName(this.loadConnectionProfiles(), this.getCurrConnProfileName()); 133 | } 134 | 135 | getProfileByName(profiles, name) { 136 | for (let i=0; profiles && i p.name !== name); 146 | localStorage.setItem(__KEY_CONN_PROFS__, JSON.stringify(updatedConnProfs)); 147 | log.debug("removeConnectionProfile"); 148 | } 149 | 150 | loadIdentities() { 151 | return this.identities; 152 | } 153 | 154 | saveIdentity(options) { 155 | const temp = new Identity(options); 156 | if (!temp.isValid()) { 157 | return; 158 | } 159 | const updatedIdentities = this.loadIdentities(); 160 | const origLabel = temp.label; 161 | 162 | // At most 1024 duplicates. 163 | for (let i=1; i<1025; i++) { 164 | if (this.getIDByName(updatedIdentities, temp.label)) { 165 | temp.label = origLabel + `(${i})`; 166 | } 167 | else { 168 | break; 169 | } 170 | } 171 | 172 | updatedIdentities.push(temp); 173 | localStorage.setItem(__KEY_IDENTITIES__, JSON.stringify(updatedIdentities)); 174 | 175 | log.debug("saveIdentity"); 176 | } 177 | 178 | getCurrIDLabel() { 179 | return this.currIDLabel; 180 | } 181 | 182 | setCurrIDLabel(currIDLabel) { 183 | localStorage.setItem(__KEY_CURR_ID__, currIDLabel); 184 | this.currIDLabel = currIDLabel; 185 | } 186 | 187 | getCurrID() { 188 | return this.getIDByName(this.loadIdentities(), this.getCurrIDLabel()); 189 | }; 190 | 191 | getIDByName(ids, label) { 192 | for (let i=0; ids && i id.label !== label); 202 | localStorage.setItem(__KEY_IDENTITIES__, JSON.stringify(updatedIdentities)); 203 | log.debug("removeIdentity"); 204 | } 205 | 206 | } 207 | 208 | const clientStorage = new ClientStorage(); 209 | 210 | export { ConnectionProfile, clientStorage}; -------------------------------------------------------------------------------- /web/src/i18n/cn.js: -------------------------------------------------------------------------------- 1 | const CN_TEXT = { 2 | at_least_one_network_identity: "至少需要一个 Connection profile 和 Identity。", 3 | connection_profile: "Connection profile", 4 | pub_key: "Public key", 5 | prv_key: "Private key", 6 | add: "Add", 7 | network: "网络", 8 | channel: "通道", 9 | chaincode: "链码" 10 | 11 | 12 | } 13 | 14 | export default CN_TEXT; -------------------------------------------------------------------------------- /web/src/i18n/en.js: -------------------------------------------------------------------------------- 1 | const EN_TEXT = { 2 | profile_setting: "Profile setting", 3 | profile_setup: "Set up profile", 4 | add_one_conn_profile: "Add one connection profile.", 5 | add_one_identity: "Add one identity.", 6 | connection_profile: "Connection profile", 7 | connection_profile_upload: "Upload a connection profile...", 8 | connection_profile_add: "Add a connection profile", 9 | id_certificate_upload: "Upload an identity certificate...", 10 | id_private_key_upload: "Upload an idenitty private key...", 11 | identity_add: "Add an identity", 12 | example_from_document: "Please find document for example and details.", 13 | msp_id: "MSP ID", 14 | id_common_name: "Common name", 15 | id_cn: "CN", 16 | id_subject: "Subject", 17 | id_issuer: "Issuer", 18 | 19 | 20 | label: "Label", 21 | add: "Add", 22 | create: "Create", 23 | join: "Join", 24 | install: "Install", 25 | instantiate: "Instantiate", 26 | upgarde: "Upgrade", 27 | execute_on: "Execute on %v", 28 | execute: "Execute", 29 | query: "Query", 30 | action: "Action", 31 | cancel: "Cancel", 32 | ok: "OK", 33 | network: "Network", 34 | example: "Example", 35 | channel: "Channel", 36 | channels: "Channels", 37 | chaincode: "Chaincode", 38 | chaincodes: "Chaincodes", 39 | arguments_input: "Input arguments one by one", 40 | version: "Version", 41 | identity: "Identity", 42 | connect_to: "Connect to...", 43 | connected: "Connected", 44 | refresh_connection: "Refresh connection", 45 | time_last_update_invalid: "The last update time is invalid.", 46 | no_result_error: "No result returned, unknown error.", 47 | status: "Status", 48 | payload: "Payload", 49 | transaction_id: "Transaction ID", 50 | transaction_validation_code: "Validation code", 51 | res_code: "Result code", 52 | err_message: "Error message", 53 | show_details: "Show details", 54 | close_details: "Close details", 55 | quick_view: "Quick view", 56 | previous_blocks: "Previous blocks", 57 | 58 | delete: "Delete", 59 | delete_confirmation: "Delete confirmation", 60 | delete_confirmation_remark: "Do you want to delete this?", 61 | 62 | chaincode_name: "Chaincode name", 63 | chaincode_version: "Chaincode version", 64 | chaincode_package: "Chaincode package", 65 | chaincode_path: "Chaincode path", 66 | chaincode_type: "Chaincode type", 67 | chaincode_install: "Install chaincode", 68 | chaincode_install_to: "You are going to install chaincode to peer %v", 69 | chaincode_install_no_result: "No chaincode install result.", 70 | chaincode_package_remark: "Chaincode sourcecode package, now only .tar and .tar.gz is supported. For Golang chaincode, " 71 | + "it should include full package path and starts with src folder. Please find document for examples.", 72 | chaincode_path_remark: "It is the Golang type chaincode package path, it can be left as blank if the Golang package path is blank. Please find document for examples.", 73 | chaincode_upload: "Upload chaincode package...", 74 | chaincode_noany: "No any chaincodes installed", 75 | chaincode_instantiate: "Instantiate chaincode", 76 | chaincode_constructor:"Chaincode constructor arguments", 77 | chaincode_policy:"Chaincode endorsement policy", 78 | chaincode_upgrade: "Upgrade chaincode", 79 | chaincode_constructor_remark: "Strings arguments, one argument per row.", 80 | chaincode_policy_remark: "Example: OR ('Org1MSP.peer','Org2MSP.peer')", 81 | chaincode_execute: "Execute chaincode", 82 | chaincode_execute_with: "%v chaincode %v on channel %v", 83 | chaincode_query: "Query chaincode", 84 | chaincode_function_name: "Function name", 85 | chaincode_arguments: "Arguments", 86 | chaincode_operation: "Chaincode operation", 87 | 88 | chaicode_status: "Chaincode status", 89 | chaincode_payload: "Chaincode payload", 90 | chaicode_peer_response: "Peer response", 91 | 92 | chaincode_type_golang: "Golang", 93 | chaincode_type_node: "Node", 94 | chaincode_type_java: "Java", 95 | 96 | chaincode_event: "Chaincode event", 97 | chaincode_event_filter: "Chaincode event filter", 98 | 99 | channel_operation: "Channel operation", 100 | channel_noany: "No channel joined yet", 101 | channel_join: "Join a channel", 102 | channel_create: "Create a channel", 103 | channel_create_transaction_upload: "Upload transaction file (.tx) of creating channel...", 104 | 105 | block_event: "Block event", 106 | block_event_monitor: "Monitor", 107 | block_event_chart_tx: "Realtime transactions chart", 108 | 109 | peer:"Peer", 110 | orderer:"Orderer", 111 | endorser: "Endorser", 112 | transaction: "Transaction", 113 | transaction_action: "Transaction action", 114 | transaction_number: "Transaction number", 115 | targets: "Targets", 116 | targets_auto: "Auto select if empty", 117 | response: "Response", 118 | isDelete: "Is delete", 119 | value: "Value", 120 | anchor: "Anchor", 121 | event: "Event", 122 | event_name: "Event name", 123 | source_url: "Source URL", 124 | 125 | proposal_response: "Proposal response", 126 | namespace: "Namespace", 127 | readset: "Readset", 128 | writeSet: "writeSet", 129 | readset_of_all: "Readset %v of %v", 130 | writeset_of_all: "Writeset %v of %v", 131 | chaincode_deployed: "Chaincode deployed", 132 | principal: "Principal", 133 | rule: "Rule", 134 | 135 | ledger_query: "Ledger query", 136 | ledger_height: "Ledger height", 137 | ledger_currentblockhash: "Ledger current hash", 138 | ledger_of_channel_target: "Ledger of channel %v, with target %v", 139 | 140 | block: "Block", 141 | block_number: "Block number", 142 | block_hash: "Block hash", 143 | block_data_hash: "Data hash", 144 | block_previous_hash: "Previous hash", 145 | block_time: "Block time", 146 | block_tx_of_all_tx: "Transaction %v of all %v", 147 | block_action_of_all_actions: "Action %v of all %v", 148 | block_endorser_of_all_endorsers: "Endorser %v of all %v", 149 | block_query_any: "Block number, hash or transaction ID", 150 | block_quantity: "Block quantity", 151 | 152 | 153 | } 154 | 155 | export default EN_TEXT; -------------------------------------------------------------------------------- /web/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import EN_TEXT from "./en"; 2 | import CN_TEXT from "./cn"; 3 | import * as CONST from "../common/constants"; 4 | 5 | function getTextProvider(loc) { 6 | switch (loc) { 7 | case CONST.__LOC_EN__: return EN_TEXT; 8 | case CONST.__LOC_CN__: return CN_TEXT; 9 | default: return EN_TEXT; 10 | } 11 | } 12 | 13 | function textWithLoc(loc, key, ...params) { 14 | let txt = getTextProvider(loc)[key] || key; 15 | if (params && params.length>0) { 16 | for (var idx in params) { 17 | txt = txt.replace("%v", params[idx]); 18 | } 19 | } 20 | return txt; 21 | } 22 | 23 | const i18n = function(key, ...params) { 24 | // TODO Local to be persisted 25 | return textWithLoc(CONST.__LOC_EN__, key, ...params); 26 | } 27 | 28 | export default i18n; -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import Main from './components/main'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 |
, 9 | document.getElementById('root') 10 | ); 11 | 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /web/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /web/src/util/wstest.js: -------------------------------------------------------------------------------- 1 | var WebSocketClient =require( "websocket").client; 2 | 3 | var client = new WebSocketClient(); 4 | 5 | client.on('connectFailed', function(error) { 6 | console.log('Connect Error: ' + error.toString()); 7 | }); 8 | 9 | client.on('connect', function(connection) { 10 | console.log('WebSocket Client Connected'); 11 | connection.on('error', function(error) { 12 | console.log("Connection Error: " + error.toString()); 13 | }); 14 | connection.on('close', function() { 15 | console.log('echo-protocol Connection Closed'); 16 | }); 17 | connection.on('message', function(message) { 18 | if (message.type === 'utf8') { 19 | console.log("Received: '" + message.utf8Data + "'"); 20 | } 21 | }); 22 | 23 | function sendNumber() { 24 | if (connection.connected) { 25 | var number = Math.round(Math.random() * 0xFFFFFF); 26 | console.log("Sending ", number) 27 | connection.sendUTF(number.toString()); 28 | setTimeout(sendNumber, 1000); 29 | } 30 | } 31 | sendNumber(); 32 | }); 33 | 34 | client.connect('ws://localhost:8080/event/realtime'); --------------------------------------------------------------------------------