├── .dockerignore ├── .env ├── .github └── settings.yml ├── .gitignore ├── .pep8 ├── .pylintrc ├── CODEOWNERS ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── SECURITY.md ├── VERSION ├── asset_client ├── Dockerfile ├── Dockerfile-installed ├── package.json ├── public │ └── index.html ├── sample_data │ ├── core_types.json │ ├── sample_data.json │ └── sample_updates.json ├── scripts │ └── generate_proto_json.js ├── src │ ├── components │ │ ├── data.js │ │ ├── forms.js │ │ ├── layout.js │ │ ├── modals.js │ │ ├── navigation.js │ │ └── tables.js │ ├── main.js │ ├── services │ │ ├── api.js │ │ ├── parsing.js │ │ ├── payloads.js │ │ └── transactions.js │ ├── utils │ │ └── records.js │ └── views │ │ ├── add_asset_form.js │ │ ├── agent_detail.js │ │ ├── asset_detail.js │ │ ├── dashboard.js │ │ ├── list_agents.js │ │ ├── list_assets.js │ │ ├── login_form.js │ │ ├── property_detail.js │ │ └── signup_form.js ├── styles │ └── main.scss └── webpack.config.js ├── bin ├── get_version ├── protogen ├── run_docker_test ├── splice_json ├── supply-chain-tp └── whitelist ├── docker-compose-installed.yaml ├── docker-compose.yaml ├── docker └── compose │ └── supply-chain-default.yaml ├── docs ├── Makefile ├── source │ ├── _static │ │ └── theme_overrides.css │ ├── _templates │ │ └── indexcontent.html │ ├── conf.py │ ├── contents.rst │ └── family_specification.rst └── supply-chain-build-docs ├── fish_client ├── Dockerfile ├── Dockerfile-installed ├── package.json ├── public │ └── index.html ├── sample_data │ ├── core_types.json │ ├── sample_data.json │ └── sample_updates.json ├── scripts │ └── generate_proto_json.js ├── src │ ├── components │ │ ├── data.js │ │ ├── forms.js │ │ ├── layout.js │ │ ├── modals.js │ │ ├── navigation.js │ │ └── tables.js │ ├── main.js │ ├── services │ │ ├── api.js │ │ ├── parsing.js │ │ ├── payloads.js │ │ └── transactions.js │ ├── utils │ │ └── records.js │ └── views │ │ ├── add_fish_form.js │ │ ├── agent_detail.js │ │ ├── dashboard.js │ │ ├── fish_detail.js │ │ ├── list_agents.js │ │ ├── list_fish.js │ │ ├── login_form.js │ │ ├── property_detail.js │ │ └── signup_form.js ├── styles │ └── main.scss └── webpack.config.js ├── images ├── rookies16-small.png └── sawtooth_logo_light_blue-small.png ├── integration └── sawtooth_integration │ ├── __init__.py │ ├── docker │ └── test_supply_chain_rust.yaml │ └── tests │ ├── __init__.py │ ├── integration_tools.py │ └── test_supply_chain.py ├── ledger_sync ├── Dockerfile ├── Dockerfile-installed-bionic ├── Dockerfile-installed-xenial ├── config.json.example ├── db │ ├── blocks.js │ ├── index.js │ └── state.js ├── index.js ├── package.json ├── subscriber │ ├── deltas.js │ ├── index.js │ └── protos.js └── system │ └── config.js ├── nose2.cfg ├── processor ├── Cargo.toml ├── Dockerfile ├── Dockerfile-installed-bionic ├── Dockerfile-installed-xenial ├── build.rs └── src │ ├── addressing.rs │ ├── handler.rs │ └── main.rs ├── protos ├── agent.proto ├── payload.proto ├── property.proto ├── proposal.proto └── record.proto ├── server ├── Dockerfile ├── Dockerfile-installed-bionic ├── Dockerfile-installed-xenial ├── api │ ├── agents.js │ ├── auth.js │ ├── errors.js │ ├── index.js │ ├── record_types.js │ ├── records.js │ └── users.js ├── blockchain │ ├── batcher.js │ ├── index.js │ └── protos.js ├── config.json.example ├── db │ ├── agents.js │ ├── index.js │ ├── record_types.js │ ├── records.js │ └── users.js ├── index.js ├── package.json ├── scripts │ ├── bootstrap_database.js │ ├── run_sample_updates.js │ ├── seed_core_types.js │ └── seed_sample_data.js └── system │ ├── config.js │ └── submit_utils.js ├── shell ├── Dockerfile ├── Dockerfile-installed-bionic └── Dockerfile-installed-xenial └── tests └── sawtooth_sc_test ├── addressing.py └── supply_chain_message_factory.py /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/npm_debug.log 3 | **/public/dist 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ISOLATION_ID=latest 2 | DISTRO=bionic 3 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | repository: 6 | name: sawtooth-supply-chain 7 | description: Archive of sawtooth-supply-chain: Sawtooth Supply Chain 8 | archived: true 9 | private: false 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude IDE and Editor files 2 | /.idea/ 3 | *.sublime* 4 | 5 | # Rust 6 | processor/Cargo.lock 7 | processor/bin/ 8 | processor/target/ 9 | processor/src/messages/ 10 | 11 | # Node 12 | **/node_modules 13 | **/package-lock.json 14 | **/npm-debug.log 15 | 16 | # Webpack 17 | **/dist 18 | 19 | tests/sawtooth_sc_test/protobuf/ 20 | 21 | /server/config.json 22 | **/generated_protos.json 23 | 24 | /docs/build/ 25 | /docs/source/_autogen/ 26 | /docs/source/cli/output/ 27 | -------------------------------------------------------------------------------- /.pep8: -------------------------------------------------------------------------------- 1 | [pep8] 2 | ignore=W503 3 | exclude=build,docs,ECDSARecoverModule.py,poet0_enclave_simulator.py,poet1_enclave_simulator.py,*_pb2.py 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @agunde406 @boydjohnson @chenette @dcmiddle @delventhalz @dplumb94 @jsmitchell @peterschwarz @rbuysse @ryanlassigbanks @vaporos 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Distributed Ledger 2 | 3 | This document covers how to report issues and contribute code. 4 | 5 | ## Topics 6 | 7 | * [Reporting Issues](#reporting-issues) 8 | * [Contributing Code](#contributing-code) 9 | 10 | # Reporting Issues 11 | 12 | Please refer to the article on [Issue Tracking](http://intelledger.github.io/community/issue_tracking.html) in the Sawtooth Lake documentation. 13 | 14 | # Contributing Code 15 | 16 | Distributed Ledger is Apache 2.0 licensed and accepts contributions via 17 | [GitHub](https://github.com/hyperledger/) pull requests. 18 | 19 | Please refer to the article on 20 | [Contributing](http://intelledger.github.io/community/contributing.html) 21 | in the Sawtooth Lake documentation for information on contributing, and the 22 | guidelines to use. -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | | Name | GitHub | RocketChat | 2 | | --- | --- | --- | 3 | | Andi Gunderson | agunde406 | agunde | 4 | | Anne Chenette | chenette | achenette | 5 | | Boyd Johnson | boydjohnson | boydjohnson | 6 | | Dan Middleton | dcmiddle | Dan | 7 | | Darian Plumb | dplumb94 | dplumb | 8 | | James Mitchell | jsmitchell | jsmitchell | 9 | | Peter Schwarz | peterschwarz | pschwarz | 10 | | Ryan Banks | RyanLassigBanks | RyanBanks | 11 | | Ryan Beck-Buysse | rbuysse | rbuysse | 12 | | Shawn Amundson | vaporos | amundson | 13 | | Zac Delventhal | delventhalz | zac | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository has been superceded by the [Hyperledger Grid](https://github.com/hyperledger/grid) project. 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Security Policy 2 | 3 | ## Reporting a Security Bug 4 | 5 | If you think you have discovered a security issue in any of the Hyperledger 6 | projects, we'd love to hear from you. We will take all security bugs 7 | seriously and if confirmed upon investigation we will patch it within a 8 | reasonable amount of time and release a public security bulletin discussing 9 | the impact and credit the discoverer. 10 | 11 | There are two ways to report a security bug. The easiest is to email a 12 | description of the flaw and any related information (e.g. reproduction 13 | steps, version) to 14 | [security at hyperledger dot org](mailto:security@hyperledger.org). 15 | 16 | The other way is to file a confidential security bug in our 17 | [JIRA bug tracking system](https://jira.hyperledger.org). 18 | Be sure to set the “Security Level” to “Security issue”. 19 | 20 | The process by which the Hyperledger Security Team handles security bugs 21 | is documented further in our 22 | [Defect Response](https://wiki.hyperledger.org/display/HYP/Defect+Response) 23 | page on our [wiki](https://wiki.hyperledger.org). 24 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.10.4 2 | -------------------------------------------------------------------------------- /asset_client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ----------------------------------------------------------------------------- 15 | 16 | # NOTE: Use `volumes` to make: asset_client/public/ 17 | # available at: /usr/local/apache2/htdocs/ 18 | 19 | FROM httpd:2.4 20 | 21 | RUN echo "\ 22 | \n\ 23 | ServerName asset_client\n\ 24 | AddDefaultCharset utf-8\n\ 25 | LoadModule proxy_module modules/mod_proxy.so\n\ 26 | LoadModule proxy_http_module modules/mod_proxy_http.so\n\ 27 | ProxyPass /api http://server:3000\n\ 28 | ProxyPassReverse /api http://server:3000\n\ 29 | \n\ 30 | " >>/usr/local/apache2/conf/httpd.conf 31 | 32 | ENV PATH $PATH:/sawtooth-supply-chain/bin 33 | 34 | EXPOSE 80/tcp 35 | -------------------------------------------------------------------------------- /asset_client/Dockerfile-installed: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ----------------------------------------------------------------------------- 15 | 16 | FROM node:8 AS static_builder 17 | 18 | WORKDIR /supply_chain/asset_client 19 | COPY asset_client/package.json . 20 | RUN npm install 21 | 22 | COPY protos/ ../protos/ 23 | COPY asset_client/ . 24 | RUN npm run build 25 | 26 | FROM httpd:2.4 27 | 28 | COPY --from=static_builder /supply_chain/asset_client/public/ /usr/local/apache2/htdocs/ 29 | 30 | RUN echo "\ 31 | \n\ 32 | ServerName asset_client\n\ 33 | AddDefaultCharset utf-8\n\ 34 | LoadModule proxy_module modules/mod_proxy.so\n\ 35 | LoadModule proxy_http_module modules/mod_proxy_http.so\n\ 36 | ProxyPass /api http://server:3000\n\ 37 | ProxyPassReverse /api http://server:3000\n\ 38 | \n\ 39 | " >>/usr/local/apache2/conf/httpd.conf 40 | 41 | EXPOSE 80/tcp 42 | -------------------------------------------------------------------------------- /asset_client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asset_track", 3 | "version": "0.0.0", 4 | "description": "A Sawtooth Supply Chain client web app for tracking assets", 5 | "main": "", 6 | "scripts": { 7 | "generate-proto-files": "node scripts/generate_proto_json.js > src/generated_protos.json", 8 | "watch": "npm run generate-proto-files && webpack-dev-server --env development --hot", 9 | "build": "npm run generate-proto-files && webpack --env production", 10 | "test": "standard src/**/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hyperledger/sawtooth-supply-chain.git" 15 | }, 16 | "author": "", 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/hyperledger/sawtooth-supply-chain/issues" 20 | }, 21 | "homepage": "https://github.com/hyperledger/sawtooth-supply-chain#readme", 22 | "dependencies": { 23 | "bootstrap": "^4.0.0-beta", 24 | "chart.js": "^2.7.0", 25 | "google-maps": "^3.2.1", 26 | "jquery": "^3.2.1", 27 | "lodash": "^4.17.4", 28 | "mithril": "^1.1.3", 29 | "moment": "^2.18.1", 30 | "octicons": "^6.0.1", 31 | "popper.js": "^1.12.3", 32 | "sawtooth-sdk": "^1.0.0-rc", 33 | "sjcl": "^1.0.7" 34 | }, 35 | "devDependencies": { 36 | "autoprefixer": "^7.1.2", 37 | "css-loader": "^0.28.5", 38 | "node-sass": "^4.5.3", 39 | "postcss-loader": "^2.0.6", 40 | "precss": "^2.0.0", 41 | "protobufjs": "^6.8.0", 42 | "sass-loader": "^6.0.6", 43 | "standard": "^10.0.3", 44 | "style-loader": "^0.18.2", 45 | "webpack": "^4.30.0", 46 | "webpack-cli": "^3.3.1", 47 | "webpack-dev-server": ">=3.1.11" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /asset_client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AssetTrack 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /asset_client/sample_data/core_types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "asset", 4 | "properties": [ 5 | { 6 | "name": "type", 7 | "dataType": 4, 8 | "required": true 9 | }, 10 | { 11 | "name": "subtype", 12 | "dataType": 4 13 | }, 14 | { 15 | "name": "weight", 16 | "dataType": 3, 17 | "numberExponent": -6 18 | }, 19 | { 20 | "name": "location", 21 | "dataType": 7 22 | }, 23 | { 24 | "name": "temperature", 25 | "dataType": 3, 26 | "numberExponent": -6 27 | }, 28 | { 29 | "name": "shock", 30 | "dataType": 3, 31 | "numberExponent": -6 32 | } 33 | ] 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /asset_client/sample_data/sample_updates.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "recordId": "7h15-45537-15-br173", 4 | "privateKey": "86f508f1e4bfd975ce747e38adb6bd8b01e5b80143520710126d4b53118c123c", 5 | "updates": [ 6 | { 7 | "name": "temperature", 8 | "dataType": 3, 9 | "value": 1000000, 10 | "isRelative": true 11 | }, 12 | 13 | { 14 | "name": "location", 15 | "dataType": 7, 16 | "value": { 17 | "latitude": 542852, 18 | "longitude": 4127038 19 | }, 20 | "isRelative": true, 21 | "isAverage": true 22 | }, 23 | 24 | { 25 | "name": "weight", 26 | "dataType": 3, 27 | "value": 10000, 28 | "noOpChance": 0.9, 29 | "isRelative": true 30 | }, 31 | 32 | { 33 | "name": "shock", 34 | "dataType": 3, 35 | "value": 50000, 36 | "isAlwaysPositive": true, 37 | "noOpChance": 0.8 38 | } 39 | ] 40 | }, 41 | 42 | { 43 | "recordId": "7h15-45537-f1135", 44 | "privateKey": "68ca3f7423f76397a27eb1b4185e808b953fc5b82cfa3eb2fd375de1c2e53b4e", 45 | "updates": [ 46 | { 47 | "name": "temperature", 48 | "dataType": 3, 49 | "value": 10000000, 50 | "isRelative": true 51 | }, 52 | 53 | { 54 | "name": "location", 55 | "dataType": 7, 56 | "value": { 57 | "latitude": -104551, 58 | "longitude": 1159712 59 | }, 60 | "isRelative": true, 61 | "isAverage": true 62 | }, 63 | 64 | { 65 | "name": "weight", 66 | "dataType": 3, 67 | "value": 500000000, 68 | "noOpChance": 0.9, 69 | "isRelative": true 70 | }, 71 | 72 | { 73 | "name": "shock", 74 | "dataType": 3, 75 | "value": 500000, 76 | "isAlwaysPositive": true, 77 | "noOpChance": 0.7 78 | } 79 | ] 80 | }, 81 | 82 | { 83 | "recordId": "7h15-45537-15-b357", 84 | "privateKey": "68ca3f7423f76397a27eb1b4185e808b953fc5b82cfa3eb2fd375de1c2e53b4e", 85 | "updates": [ 86 | { 87 | "name": "temperature", 88 | "dataType": 3, 89 | "value": 10000, 90 | "isRelative": true 91 | }, 92 | 93 | { 94 | "name": "location", 95 | "dataType": 7, 96 | "value": { 97 | "latitude": 1007305, 98 | "longitude": 2509187 99 | }, 100 | "isRelative": true, 101 | "isAverage": true 102 | }, 103 | 104 | { 105 | "name": "weight", 106 | "dataType": 3, 107 | "value": 1000, 108 | "noOpChance": 0.9, 109 | "isRelative": true 110 | }, 111 | 112 | { 113 | "name": "shock", 114 | "dataType": 3, 115 | "value": 50000, 116 | "isAlwaysPositive": true, 117 | "noOpChance": 0.9 118 | } 119 | ] 120 | } 121 | ] 122 | -------------------------------------------------------------------------------- /asset_client/scripts/generate_proto_json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ------------------------------------------------------------------------------ 16 | */ 17 | 'use strict' 18 | 19 | const fs = require('fs') 20 | const path = require('path') 21 | const protobuf = require('protobufjs') 22 | const jsonTarget = require('../node_modules/protobufjs/cli/targets/json') 23 | 24 | let root = new protobuf.Root() 25 | 26 | let files = fs 27 | .readdirSync(path.resolve(__dirname, '../../protos')) 28 | .map(f => path.resolve(__dirname, '../../protos', f)) 29 | .filter(f => f.endsWith('.proto')) 30 | 31 | try { 32 | root = root.loadSync(files) 33 | } catch (e) { 34 | console.log('Unable to load protobuf files!') 35 | throw e 36 | } 37 | 38 | jsonTarget(root, {}, (err, output) => { 39 | if (err) { 40 | throw err 41 | } 42 | 43 | if (output !== '') { 44 | process.stdout.write(output, 'utf8') 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /asset_client/src/components/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const octicons = require('octicons') 22 | 23 | /** 24 | * Returns a header styled to be a page title 25 | */ 26 | const title = title => m('h3.text-center.mb-3', title) 27 | 28 | /** 29 | * Returns a row of any number of columns, suitable for placing in a container 30 | */ 31 | const row = columns => { 32 | if (!_.isArray(columns)) columns = [columns] 33 | return m('.row', columns.map(col => m('.col-md', col))) 34 | } 35 | 36 | /** 37 | * Returns a mithriled icon from Github's octicon set 38 | */ 39 | const icon = name => m.trust(octicons[name].toSVG()) 40 | 41 | module.exports = { 42 | title, 43 | row, 44 | icon 45 | } 46 | -------------------------------------------------------------------------------- /asset_client/src/components/modals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const $ = require('jquery') 22 | 23 | /** 24 | * A basic Bootstrap modal. Requires at least a title and body be set in 25 | * attributes. Also accepts text and functions for accepting/canceling. 26 | */ 27 | const BasicModal = { 28 | view (vnode) { 29 | // Set default accept/cancel values 30 | const acceptText = vnode.attrs.acceptText || 'Accept' 31 | const cancelText = vnode.attrs.cancelText || 'Cancel' 32 | const acceptFn = vnode.attrs.acceptFn || _.identity 33 | const cancelFn = vnode.attrs.cancelFn || _.identity 34 | 35 | return m('.modal.fade#modal', { 36 | tabindex: '-1', 37 | role: 'dialog', 38 | 'aria-labelby': 'modal' 39 | }, [ 40 | m('.modal-dialog', { role: 'document' }, 41 | m('form', 42 | m('.modal-content', 43 | m('.modal-header', 44 | m('h5.modal-title', vnode.attrs.title), 45 | m('button.close', { 46 | type: 'button', 47 | onclick: cancelFn, 48 | 'data-dismiss': 'modal', 49 | 'aria-label': 'Close' 50 | }, m('span', { 'aria-hidden': 'true' }, m.trust('×'))) 51 | ), 52 | m('.modal-body', vnode.attrs.body), 53 | m('.modal-footer', 54 | m('button.btn.btn-secondary', { 55 | type: 'button', 56 | onclick: cancelFn, 57 | 'data-dismiss': 'modal' 58 | }, cancelText), 59 | m('button.btn.btn-primary', { 60 | type: 'submit', 61 | onclick: acceptFn, 62 | 'data-dismiss': 'modal' 63 | }, acceptText))))) 64 | ]) 65 | } 66 | } 67 | 68 | /** 69 | * Renders/shows a modal component, with attributes, returning a promise. 70 | * On close, unmounts the component and resolves/rejects the promise, 71 | * with rejection indicating the cancel button was pressed. 72 | */ 73 | const show = (modal, attrs, children) => { 74 | let acceptFn = null 75 | let cancelFn = null 76 | const onClosePromise = new Promise((resolve, reject) => { 77 | acceptFn = resolve 78 | cancelFn = reject 79 | }) 80 | 81 | const container = document.getElementById('modal-container') 82 | m.render(container, 83 | m(modal, _.assign(attrs, { acceptFn, cancelFn }, children))) 84 | const $modal = $('#modal') 85 | $modal.on('hidden.bs.modal', () => m.mount(container, null)) 86 | $modal.modal('show') 87 | 88 | return onClosePromise 89 | } 90 | 91 | module.exports = { 92 | BasicModal, 93 | show 94 | } 95 | -------------------------------------------------------------------------------- /asset_client/src/components/navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | /** 22 | * A simple navbar wrapper, which displays children in the responsive collapse. 23 | */ 24 | const Navbar = { 25 | view (vnode) { 26 | return m('nav.navbar.navbar-expand-sm.navbar-dark.bg-dark.mb-5', [ 27 | m('a.navbar-brand[href="/"]', { oncreate: m.route.link }, 'AssetTrack'), 28 | m('button.navbar-toggler', { 29 | type: 'button', 30 | 'data-toggle': 'collapse', 31 | 'data-target': '#navbar', 32 | 'aria-controls': 'navbar', 33 | 'aria-expanded': 'false', 34 | 'aria-label': 'Toggle navigation' 35 | }, m('span.navbar-toggler-icon')), 36 | m('#navbar.collapse.navbar-collapse', vnode.children) 37 | ]) 38 | } 39 | } 40 | 41 | /** 42 | * Creates a list of left-aligned navbar links from href/label tuples. 43 | */ 44 | const links = items => { 45 | return m('ul.navbar-nav.mr-auto', items.map(([href, label]) => { 46 | return m('li.nav-item', [ 47 | m('a.nav-link', { href, oncreate: m.route.link }, label) 48 | ]) 49 | })) 50 | } 51 | 52 | /** 53 | * Creates a single link for use in a navbar. 54 | */ 55 | const link = (href, label) => { 56 | return m('.navbar-nav', [ 57 | m('a.nav-link', { href, oncreate: m.route.link }, label) 58 | ]) 59 | } 60 | 61 | /** 62 | * Creates a navigation button styled for use in the navbar. 63 | */ 64 | const button = (href, label) => { 65 | return m('a.btn.btn-outline-primary.my-2.my-sm-0', { 66 | href, 67 | oncreate: m.route.link 68 | }, label) 69 | } 70 | 71 | module.exports = { 72 | Navbar, 73 | link, 74 | links, 75 | button 76 | } 77 | -------------------------------------------------------------------------------- /asset_client/src/components/tables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const Table = { 22 | oninit (vnode) { 23 | if (!vnode.attrs.noRowsText) { 24 | vnode.attrs.noRowsText = 'No rows available' 25 | } 26 | }, 27 | 28 | view (vnode) { 29 | return [ 30 | m('table.table', 31 | m('thead', 32 | m('tr', 33 | vnode.attrs.headers.map((header) => m('th', header)))), 34 | m('tbody', 35 | vnode.attrs.rows.length > 0 36 | ? vnode.attrs.rows 37 | .map((row) => 38 | m('tr', 39 | row.map((col) => m('td', col)))) 40 | : m('tr', 41 | m('td.text-center', {colSpan: 5}, 42 | vnode.attrs.noRowsText)) 43 | ) 44 | ) 45 | ] 46 | } 47 | } 48 | 49 | const FilterGroup = { 50 | oninit (vnode) { 51 | vnode.state.currentFilter = vnode.attrs.initialFilter 52 | }, 53 | 54 | view (vnode) { 55 | return [ 56 | m('.btn-group', { 57 | role: 'group', 58 | 'aria-label': vnode.attrs.ariaLabel 59 | }, 60 | Object.entries(vnode.attrs.filters).map(([label, action]) => 61 | m('button.btn', { 62 | className: vnode.state.currentFilter === label ? 'btn-primary' : 'btn-light', 63 | onclick: (e) => { 64 | e.preventDefault() 65 | vnode.state.currentFilter = label 66 | action() 67 | } 68 | }, label))) 69 | ] 70 | } 71 | } 72 | 73 | const PagingButtons = { 74 | view (vnode) { 75 | return [ 76 | m('.d-flex.justify-content-end', 77 | m('.btn-group', { 78 | role: 'group', 79 | 'aria-label': 'Paging controls' 80 | }, 81 | m('button.btn.btn-light', { 82 | onclick: (e) => { 83 | e.preventDefault() 84 | vnode.attrs.setPage(Math.max(0, vnode.attrs.currentPage - 1)) 85 | }, 86 | disabled: vnode.attrs.currentPage === 0 87 | }, '\u25c0'), // right arrow 88 | m('button.btn.btn-light', { 89 | onclick: (e) => { 90 | e.preventDefault() 91 | vnode.attrs.setPage( 92 | Math.min(vnode.attrs.maxPage, vnode.attrs.currentPage + 1)) 93 | }, 94 | disabled: (vnode.attrs.currentPage === vnode.attrs.maxPage) 95 | }, '\u25b6'))) // left arrow 96 | ] 97 | } 98 | } 99 | 100 | module.exports = { 101 | Table, 102 | FilterGroup, 103 | PagingButtons 104 | } 105 | -------------------------------------------------------------------------------- /asset_client/src/services/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const sjcl = require('sjcl') 22 | 23 | const API_PATH = 'api/' 24 | const STORAGE_KEY = 'asset_track.authorization' 25 | let authToken = null 26 | 27 | /** 28 | * Generates a base-64 encoded SHA-256 hash of a plain text password 29 | * for submission to authorization routes 30 | */ 31 | const hashPassword = password => { 32 | const bits = sjcl.hash.sha256.hash(password) 33 | return sjcl.codec.base64.fromBits(bits) 34 | } 35 | 36 | /** 37 | * Getters and setters to handle the auth token both in memory and storage 38 | */ 39 | const getAuth = () => { 40 | if (!authToken) { 41 | authToken = window.localStorage.getItem(STORAGE_KEY) 42 | } 43 | return authToken 44 | } 45 | 46 | const setAuth = token => { 47 | window.localStorage.setItem(STORAGE_KEY, token) 48 | authToken = token 49 | return authToken 50 | } 51 | 52 | const clearAuth = () => { 53 | const token = getAuth() 54 | window.localStorage.clear(STORAGE_KEY) 55 | authToken = null 56 | return token 57 | } 58 | 59 | /** 60 | * Parses the authToken to return the logged in user's public key 61 | */ 62 | const getPublicKey = () => { 63 | const token = getAuth() 64 | if (!token) return null 65 | return window.atob(token.split('.')[1]) 66 | } 67 | 68 | // Adds Authorization header and prepends API path to url 69 | const baseRequest = opts => { 70 | const Authorization = getAuth() 71 | const authHeader = Authorization ? { Authorization } : {} 72 | opts.headers = _.assign(opts.headers, authHeader) 73 | opts.url = API_PATH + opts.url 74 | return m.request(opts) 75 | } 76 | 77 | /** 78 | * Submits a request to an api endpoint with an auth header if present 79 | */ 80 | const request = (method, endpoint, data) => { 81 | return baseRequest({ 82 | method, 83 | url: endpoint, 84 | data 85 | }) 86 | } 87 | 88 | /** 89 | * Method specific versions of request 90 | */ 91 | const get = _.partial(request, 'GET') 92 | const post = _.partial(request, 'POST') 93 | const patch = _.partial(request, 'PATCH') 94 | 95 | /** 96 | * Method for posting a binary file to the API 97 | */ 98 | const postBinary = (endpoint, data) => { 99 | return baseRequest({ 100 | method: 'POST', 101 | url: endpoint, 102 | headers: { 'Content-Type': 'application/octet-stream' }, 103 | // prevent Mithril from trying to JSON stringify the body 104 | serialize: x => x, 105 | data 106 | }) 107 | } 108 | 109 | module.exports = { 110 | hashPassword, 111 | getAuth, 112 | setAuth, 113 | clearAuth, 114 | getPublicKey, 115 | request, 116 | get, 117 | post, 118 | patch, 119 | postBinary 120 | } 121 | -------------------------------------------------------------------------------- /asset_client/src/services/parsing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const moment = require('moment') 21 | const { FLOAT_PRECISION } = require('./payloads') 22 | 23 | const STRINGIFIERS = { 24 | LOCATION: v => `${v.latitude}, ${v.longitude}`, 25 | weight: v => `${v}kg`, 26 | temperature: v => `${v} °C`, 27 | shock: v => `${v}g`, 28 | '*': v => JSON.stringify(v, null, 1).replace(/[{}"]/g, '') 29 | } 30 | 31 | /** 32 | * Parses a property value by its name or type, returning a string for display 33 | */ 34 | const stringifyValue = (value, type, name) => { 35 | if (STRINGIFIERS[type]) { 36 | return STRINGIFIERS[type](value) 37 | } 38 | if (STRINGIFIERS[name]) { 39 | return STRINGIFIERS[name](value) 40 | } 41 | return STRINGIFIERS['*'](value) 42 | } 43 | 44 | /** 45 | * Simple functions that turn numbers or number-like strings to 46 | * an integer (in millionths) or back to a float. 47 | */ 48 | const toFloat = num => parseInt(num) / FLOAT_PRECISION 49 | const toInt = num => parseInt(parseFloat(num) * FLOAT_PRECISION) 50 | 51 | /** 52 | * Calls toFloat on a property value, or it's sub-values in the case of 53 | * an object or JSON object 54 | */ 55 | const floatifyValue = value => { 56 | if (_.isString(value)) value = JSON.parse(value) 57 | if (_.isObject(value)) return _.mapValues(value, toFloat) 58 | return toFloat(value) 59 | } 60 | 61 | /** 62 | * Parses seconds into a date/time string 63 | */ 64 | const formatTimestamp = sec => { 65 | if (!sec) { 66 | sec = Date.now() / 1000 67 | } 68 | return moment.unix(sec).format('MM/DD/YYYY, h:mm:ss a') 69 | } 70 | 71 | module.exports = { 72 | toInt, 73 | toFloat, 74 | stringifyValue, 75 | floatifyValue, 76 | formatTimestamp 77 | } 78 | -------------------------------------------------------------------------------- /asset_client/src/utils/records.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _getProp = (record, propName) => { 20 | return record.properties.find((prop) => prop.name === propName) 21 | } 22 | 23 | const getPropertyValue = (record, propName, defaultValue = null) => { 24 | let prop = _getProp(record, propName) 25 | if (prop && prop.value) { 26 | return prop.value 27 | } else { 28 | return defaultValue 29 | } 30 | } 31 | 32 | const isReporter = (record, propName, publicKey) => { 33 | let prop = _getProp(record, propName) 34 | if (prop) { 35 | return prop.reporters.indexOf(publicKey) > -1 36 | } else { 37 | return false 38 | } 39 | } 40 | 41 | const _getPropTimeByComparison = (compare) => (record) => { 42 | if (!record.updates.properties) { 43 | return null 44 | } 45 | 46 | return Object.values(record.updates.properties) 47 | .reduce((acc, updates) => acc.concat(updates), []) 48 | .reduce((selected, update) => 49 | compare(selected.timestamp, update.timestamp) ? update : selected) 50 | .timestamp 51 | } 52 | 53 | const getLatestPropertyUpdateTime = 54 | _getPropTimeByComparison((selected, timestamp) => selected < timestamp) 55 | 56 | const getOldestPropertyUpdateTime = 57 | _getPropTimeByComparison((selected, timestamp) => selected > timestamp) 58 | 59 | const countPropertyUpdates = (record) => { 60 | if (!record.updates.properties) { 61 | return 0 62 | } 63 | 64 | return Object.values(record.updates.properties).reduce( 65 | (sum, updates) => sum + updates.length, 0) 66 | } 67 | 68 | module.exports = { 69 | getPropertyValue, 70 | isReporter, 71 | getLatestPropertyUpdateTime, 72 | getOldestPropertyUpdateTime, 73 | countPropertyUpdates 74 | } 75 | -------------------------------------------------------------------------------- /asset_client/src/views/dashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const Dashboard = { 22 | view (vnode) { 23 | return [ 24 | m('.header.text-center.mb-4', 25 | m('h4', 'Welcome To'), 26 | m('h1.mb-3', 'AssetTrack'), 27 | m('h5', 28 | m('em', 29 | 'Powered by ', 30 | m('strong', 'Sawtooth Supply Chain')))), 31 | m('.blurb', 32 | m('p', 33 | m('em', 'Sawtooth Supply Chain'), 34 | ' is a general purpose supply chain solution built using the ', 35 | 'power of ', 36 | m('a[href="https://github.com/hyperledger/sawtooth-core"]', 37 | { target: '_blank' }, 38 | "Hyperledger Sawtooth's"), 39 | ' blockchain technology. It maintains a distributed ledger ', 40 | 'that tracks both asset provenance and a timestamped history ', 41 | 'detailing how an asset was stored, handled, and transported.'), 42 | m('p', 43 | m('em', 'AssetTrack'), 44 | ' demonstrates this unique technology in a simple web app. ', 45 | 'It allows generic assets to be added to the blockchain and ', 46 | 'tracked through their life cycle. As ownership changes hands, ', 47 | 'location changes, or even if there is a spike in temperature ', 48 | 'during storage, an update can be submitted to the distributed ', 49 | 'ledger, providing an immutable auditable history.'), 50 | m('p', 51 | 'To use ', 52 | m('em', 'AssetTrack'), 53 | ', create an account using the link in the navbar above. ', 54 | 'Once logged in, you will be able to add new assets to ', 55 | 'the blockchain and track them with data like temperature or ', 56 | 'location. You will be able to authorize other "agents" on the ', 57 | 'blockchain to track this data as well, or even transfer ', 58 | 'ownership or possession of the asset entirely. For the ', 59 | 'adventurous, these actions can also be accomplished directly ', 60 | 'with the REST API running on the ', 61 | m('em', 'Supply Chain'), 62 | ' server, perfect for automated IoT sensors.')) 63 | ] 64 | } 65 | } 66 | 67 | module.exports = Dashboard 68 | -------------------------------------------------------------------------------- /asset_client/src/views/list_agents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const sortBy = require('lodash/sortBy') 21 | const truncate = require('lodash/truncate') 22 | const {Table, FilterGroup, PagingButtons} = require('../components/tables.js') 23 | const api = require('../services/api') 24 | 25 | const PAGE_SIZE = 50 26 | 27 | const AgentList = { 28 | oninit (vnode) { 29 | vnode.state.agents = [] 30 | vnode.state.filteredAgents = [] 31 | vnode.state.currentPage = 0 32 | 33 | const refresh = () => { 34 | api.get('agents').then((agents) => { 35 | vnode.state.agents = sortBy(agents, 'name') 36 | vnode.state.filteredAgents = vnode.state.agents 37 | }) 38 | .then(() => { vnode.state.refreshId = setTimeout(refresh, 2000) }) 39 | } 40 | 41 | refresh() 42 | }, 43 | 44 | onbeforeremove (vnode) { 45 | clearTimeout(vnode.state.refreshId) 46 | }, 47 | 48 | view (vnode) { 49 | let publicKey = api.getPublicKey() 50 | return [ 51 | m('.agent-list', 52 | m('.row.btn-row.mb-2', _controlButtons(vnode, publicKey)), 53 | m(Table, { 54 | headers: [ 55 | 'Name', 56 | 'Key', 57 | 'Owns', 58 | 'Custodian', 59 | 'Reports' 60 | ], 61 | rows: vnode.state.filteredAgents.slice( 62 | vnode.state.currentPage * PAGE_SIZE, 63 | (vnode.state.currentPage + 1) * PAGE_SIZE) 64 | .map((agent) => [ 65 | m(`a[href=/agents/${agent.key}]`, { oncreate: m.route.link }, 66 | truncate(agent.name, { length: 32 })), 67 | truncate(agent.key, { length: 32 }), 68 | agent.owns.length, 69 | agent.custodian.length, 70 | agent.reports.length 71 | ]), 72 | noRowsText: 'No agents found' 73 | }) 74 | ) 75 | ] 76 | } 77 | } 78 | 79 | const _controlButtons = (vnode, publicKey) => { 80 | if (publicKey) { 81 | let filterAgents = (f) => { 82 | vnode.state.filteredAgents = vnode.state.agents.filter(f) 83 | } 84 | 85 | return [ 86 | m('.col-sm-8', 87 | m(FilterGroup, { 88 | ariaLabel: 'Filter Based on Ownership', 89 | filters: { 90 | 'All': () => { vnode.state.filteredAgents = vnode.state.agents }, 91 | 'Owners': () => filterAgents(agent => agent.owns.length > 0), 92 | 'Custodians': () => filterAgents(agent => agent.custodian.length > 0), 93 | 'Reporters': () => filterAgents(agent => agent.reports.length > 0) 94 | }, 95 | initialFilter: 'All' 96 | })), 97 | m('.col-sm-4', _pagingButtons(vnode)) 98 | ] 99 | } else { 100 | return [ 101 | m('.col-sm-4.ml-auto', _pagingButtons(vnode)) 102 | ] 103 | } 104 | } 105 | 106 | const _pagingButtons = (vnode) => 107 | m(PagingButtons, { 108 | setPage: (page) => { vnode.state.currentPage = page }, 109 | currentPage: vnode.state.currentPage, 110 | maxPage: Math.floor(vnode.state.filteredAgents.length / PAGE_SIZE) 111 | }) 112 | 113 | module.exports = AgentList 114 | -------------------------------------------------------------------------------- /asset_client/src/views/login_form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const api = require('../services/api') 22 | const transactions = require('../services/transactions') 23 | const forms = require('../components/forms') 24 | 25 | /** 26 | * The Form for authorizing an existing user. 27 | */ 28 | const LoginForm = { 29 | view (vnode) { 30 | const setter = forms.stateSetter(vnode.state) 31 | 32 | return m('.login-form', [ 33 | m('form', { 34 | onsubmit: (e) => { 35 | e.preventDefault() 36 | const credentials = { 37 | username: vnode.state.username, 38 | password: api.hashPassword(vnode.state.password) 39 | } 40 | api.post('authorization', credentials) 41 | .then(res => { 42 | api.setAuth(res.authorization) 43 | transactions.setPrivateKey(vnode.state.password, 44 | res.encryptedKey) 45 | m.route.set('/') 46 | }) 47 | } 48 | }, 49 | m('legend', 'Login Agent'), 50 | forms.textInput(setter('username'), 'Username'), 51 | forms.passwordInput(setter('password'), 'Password'), 52 | m('.container.text-center', 53 | 'Or you can ', 54 | m('a[href="/signup"]', 55 | { oncreate: m.route.link }, 56 | 'create a new Agent')), 57 | m('.form-group', 58 | m('.row.justify-content-end.align-items-end', 59 | m('col-2', 60 | m('button.btn.btn-primary', 61 | {'data-toggle': 'modal', 'data-target': '#modal'}, 62 | 'Login'))))) 63 | ]) 64 | } 65 | } 66 | 67 | module.exports = LoginForm 68 | -------------------------------------------------------------------------------- /asset_client/src/views/signup_form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | 22 | const forms = require('../components/forms') 23 | const api = require('../services/api') 24 | const transactions = require('../services/transactions') 25 | const payloads = require('../services/payloads') 26 | 27 | const passwordCard = state => { 28 | const setter = forms.stateSetter(state) 29 | const validator = forms.validator( 30 | () => state.password === state.confirm, 31 | 'Passwords do not match', 32 | 'confirm' 33 | ) 34 | const passwordField = (id, placeholder) => { 35 | return forms.field( 36 | // Run both state setting and validation on value changes 37 | _.flow(setter(id), validator), 38 | { 39 | id, 40 | placeholder, 41 | type: 'password', 42 | class: 'border-warning' 43 | } 44 | ) 45 | } 46 | 47 | return forms.group('Password', [ 48 | m('.card.text-center.border-warning', 49 | m('.card-header.text-white.bg-warning', m('em', m('strong', 'WARNING!'))), 50 | m('.card-body.text-warning.bg-light', 51 | m('p.card-text', 52 | 'This password will be used as a secret key to encrypt important ', 53 | 'account information. Although it can be changed later, ', 54 | m('em', 55 | 'if lost or forgotten it will be ', 56 | m('strong', 'impossible'), 57 | ' to recover your account.')), 58 | m('p.card-text', 'Keep it secure.'), 59 | passwordField('password', 'Enter password...'), 60 | passwordField('confirm', 'Confirm password...'))) 61 | ]) 62 | } 63 | 64 | const userSubmitter = state => e => { 65 | e.preventDefault() 66 | 67 | const keys = transactions.makePrivateKey(state.password) 68 | const user = _.assign(keys, _.pick(state, 'username', 'email')) 69 | user.password = api.hashPassword(state.password) 70 | const agent = payloads.createAgent(_.pick(state, 'name')) 71 | 72 | transactions.submit(agent, true) 73 | .then(() => api.post('users', user)) 74 | .then(res => api.setAuth(res.authorization)) 75 | .then(() => m.route.set('/')) 76 | } 77 | 78 | /** 79 | * The Form for authorizing an existing user. 80 | */ 81 | const SignupForm = { 82 | view (vnode) { 83 | const setter = forms.stateSetter(vnode.state) 84 | 85 | return m('.signup-form', [ 86 | m('form', { onsubmit: userSubmitter(vnode.state) }, 87 | m('legend', 'Create Agent'), 88 | forms.textInput(setter('name'), 'Name'), 89 | forms.emailInput(setter('email'), 'Email'), 90 | forms.textInput(setter('username'), 'Username'), 91 | passwordCard(vnode.state), 92 | m('.container.text-center', 93 | 'Or you can ', 94 | m('a[href="/login"]', 95 | { oncreate: m.route.link }, 96 | 'login an existing Agent')), 97 | m('.form-group', 98 | m('.row.justify-content-end.align-items-end', 99 | m('col-2', 100 | m('button.btn.btn-primary', 101 | 'Create Agent'))))) 102 | ]) 103 | } 104 | } 105 | 106 | module.exports = SignupForm 107 | -------------------------------------------------------------------------------- /asset_client/styles/main.scss: -------------------------------------------------------------------------------- 1 | // Styling for Google Maps API 2 | #map-container { 3 | height: 400px; 4 | width: 100%; 5 | } 6 | 7 | @import "~bootstrap/scss/bootstrap"; 8 | @import "~octicons/build/octicons.min"; 9 | -------------------------------------------------------------------------------- /asset_client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main', 6 | 7 | output: { 8 | path: path.resolve(__dirname, 'public/dist'), 9 | filename: 'bundle.js' 10 | }, 11 | 12 | module: { 13 | rules: [{ 14 | test: /\.(scss)$/, 15 | use: [{ 16 | loader: 'style-loader' 17 | }, { 18 | loader: 'css-loader' 19 | }, { 20 | loader: 'postcss-loader', 21 | options: { 22 | plugins: () => [ 23 | require('precss'), 24 | require('autoprefixer') 25 | ] 26 | } 27 | }, { 28 | loader: 'sass-loader' 29 | }] 30 | }] 31 | }, 32 | 33 | plugins: [ 34 | new webpack.ProvidePlugin({ 35 | $: 'jquery', 36 | jQuery: 'jquery', 37 | 'window.jQuery': 'jquery', 38 | Popper: ['popper.js', 'default'] 39 | }) 40 | ], 41 | 42 | devServer: { 43 | port: 3001, 44 | contentBase: path.join(__dirname, 'public'), 45 | publicPath: '/dist/', 46 | proxy: { 47 | '/api': 'http://localhost:3000' 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bin/get_version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2016, 2017 Intel Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # ------------------------------------------------------------------------------ 17 | 18 | import os 19 | import subprocess 20 | import sys 21 | 22 | top_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 23 | 24 | version_file = top_dir + "/VERSION" 25 | 26 | with open(version_file, 'r') as f: 27 | version_data = f.read().strip() 28 | 29 | 30 | def bump_version(version): 31 | (major, minor, patch) = version.split('.') 32 | if 'rc' in patch: 33 | parts = patch.split('rc') 34 | parts[1] = str(int(parts[1]) + 1) 35 | patch = "rc".join(parts) 36 | else: 37 | patch = str(int(patch) + 1) 38 | return ".".join([major, minor, patch]) 39 | 40 | 41 | def auto_version(default, strict): 42 | output = subprocess.check_output(['git', 'describe', '--dirty']) 43 | parts = output.decode('utf-8').strip().split('-', 3) 44 | parts[0] = parts[0][1:] # strip the leading 'v' 45 | if len(parts) > 1: 46 | parts[0] = bump_version(parts[0]) 47 | if default != parts[0]: 48 | msg = "VERSION file and (bumped?) git describe versions differ: " \ 49 | "{} != {}".format(default, parts[0]) 50 | if strict: 51 | print("ERROR: " + msg, file=sys.stderr) 52 | sys.exit(1) 53 | else: 54 | print("WARNING: " + msg, file=sys.stderr) 55 | print( 56 | "WARNING: using setup.py version {}".format(default), 57 | file=sys.stderr) 58 | parts[0] = default 59 | 60 | if len(parts) > 1: 61 | parts[0] = ".dev".join([parts[0], parts[1].replace("-", ".")]) 62 | if len(parts) == 4: 63 | parts[0] = parts[0] + "-" + parts[3] 64 | return parts[0] 65 | else: 66 | return parts[0] 67 | 68 | 69 | def version(default): 70 | if 'VERSION' in os.environ: 71 | if os.environ['VERSION'] == 'AUTO_STRICT': 72 | version = auto_version(default, strict=True) 73 | elif os.environ['VERSION'] == 'AUTO': 74 | version = auto_version(default, strict=False) 75 | else: 76 | version = os.environ['VERSION'] 77 | else: 78 | version = default + ".dev1" 79 | return version 80 | 81 | 82 | print(version(version_data)) 83 | -------------------------------------------------------------------------------- /bin/protogen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2017 Intel Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # ------------------------------------------------------------------------------ 17 | 18 | import os 19 | import tempfile 20 | from glob import glob 21 | import re 22 | import subprocess 23 | import sys 24 | 25 | 26 | try: 27 | from grpc.tools.protoc import main as _protoc 28 | except ImportError: 29 | print("Error: grpc.tools.protoc not found") 30 | exit(1) 31 | 32 | 33 | JOIN = os.path.join 34 | TOP_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 35 | 36 | 37 | def main(args=None): 38 | if args is None: 39 | args = sys.argv[1:] 40 | 41 | # Generate protobuf classes for Python integration tests 42 | protoc( 43 | JOIN(TOP_DIR, "protos"), 44 | "tests", 45 | "sawtooth_sc_test/protobuf") 46 | 47 | 48 | def protoc(src_dir, base_dir, pkg, language="python"): 49 | if language == "python": 50 | protoc_python(src_dir, base_dir, pkg) 51 | 52 | 53 | def protoc_python(src_dir, base_dir, pkg): 54 | # 1. Create output package directory 55 | pkg_dir = JOIN(TOP_DIR, base_dir, pkg) 56 | os.makedirs(pkg_dir, exist_ok=True) 57 | 58 | # 2. 'touch' the __init__.py file if the output directory exists 59 | init_py = JOIN(pkg_dir, "__init__.py") 60 | if not os.path.exists(init_py): 61 | with open(init_py, "w") as fd: 62 | pass # Just need it to exist 63 | 64 | # 3. Create a temp directory for building 65 | with tempfile.TemporaryDirectory() as tmp_dir: 66 | tmp_pkg_dir = JOIN(tmp_dir, pkg) 67 | os.makedirs(tmp_pkg_dir) 68 | 69 | # 4. Get a list of all .proto files to build 70 | cwd = os.getcwd() 71 | os.chdir(src_dir) 72 | proto_files = glob("*.proto") 73 | os.chdir(cwd) 74 | 75 | # 5. Copy protos to temp dir and fix imports 76 | for proto in proto_files: 77 | src = JOIN(src_dir, proto) 78 | dst = JOIN(tmp_pkg_dir, proto) 79 | with open(src, encoding='utf-8') as fin: 80 | with open(dst, "w", encoding='utf-8') as fout: 81 | src_contents = fin.read() 82 | fixed_contents = fix_import(src_contents, pkg) 83 | fout.write(fixed_contents) 84 | 85 | # 6. Compile protobuf files 86 | _protoc([ 87 | __file__, 88 | "-I=%s" % tmp_dir, 89 | "--python_out=%s" % JOIN(TOP_DIR, base_dir), 90 | ] + glob("%s/*.proto" % tmp_pkg_dir)) 91 | 92 | 93 | def fix_import(contents, pkg, sub_dir=False): 94 | pattern = r'^import "(.*)\.proto\"' 95 | if sub_dir: 96 | template = r'import "%s/\1_pb2/\1.proto"' 97 | else: 98 | template = r'import "%s/\1.proto"' 99 | 100 | return re.sub( 101 | pattern, 102 | lambda match: match.expand(template) % pkg, 103 | contents, 104 | flags=re.MULTILINE 105 | ) 106 | 107 | 108 | if __name__ == "__main__": 109 | try: 110 | main() 111 | except KeyboardInterrupt: 112 | exit(1) 113 | -------------------------------------------------------------------------------- /bin/splice_json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright 2017 Intel Corporation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * ---------------------------------------------------------------------------- 18 | */ 19 | 20 | const { resolve } = require('path') 21 | 22 | const baseDir = process.cwd() 23 | const jsonPaths = process.argv.slice(2) 24 | 25 | // Deeply combines objects, overwriting primitive data, but recursively 26 | // checking nested objects for unique keys. For example: 27 | // {a: 1, b: {c: 2}} + {a: 3, b: {x: 5}} -> {a: 3, b: {c: 2, x: 5}} 28 | const spliceObjects = (a, b) => { 29 | return Object.keys(b).reduce((spliced, key) => { 30 | if (typeof a[key] === 'object' && typeof b[key] === 'object') { 31 | spliced[key] = spliceObjects(a[key], b[key]) 32 | } else { 33 | spliced[key] = b[key] 34 | } 35 | return spliced 36 | }, Object.assign({}, a)) 37 | } 38 | 39 | // Splice the JSON files specified then sent to stdout as JSON string 40 | process.stdout.write(JSON.stringify(jsonPaths 41 | .map(relPath => resolve(baseDir, relPath)) 42 | .map(absPath => require(absPath)) 43 | .reduce((spliced, jsonObj) => spliceObjects(spliced, jsonObj), {}))) 44 | -------------------------------------------------------------------------------- /bin/supply-chain-tp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2018 Cargill Incorporated 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | top_dir=$(cd $(dirname $(dirname $0)) && pwd) 17 | bin=$top_dir/processor/bin/supply-chain-tp 18 | 19 | if [ -e $bin ] 20 | then 21 | $bin $* 22 | else 23 | echo "Please build supply-chain-tp first with 'build_all' or 'build_rust'" 24 | fi 25 | -------------------------------------------------------------------------------- /bin/whitelist: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z $1 || -z $2 ]] 4 | then 5 | echo "USAGE: $0 [user] [whitelist]" 6 | exit 1 7 | fi 8 | 9 | whitelist=$(cat $2 | grep user | sed 's#.*: \(.*$\)#\1#') 10 | for user in $whitelist 11 | do 12 | if [[ $user == $1 ]] 13 | then 14 | echo "SUCCESS: User '$1' whitelisted" 15 | exit 0 16 | fi 17 | done 18 | 19 | echo "FAILED: User '$1' not whitelisted." 20 | exit 1 21 | -------------------------------------------------------------------------------- /docker-compose-installed.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | version: '2.1' 17 | 18 | services: 19 | 20 | processor: 21 | image: supply-tp-installed:${ISOLATION_ID} 22 | container_name: supply-tp-installed 23 | build: 24 | context: . 25 | dockerfile: processor/Dockerfile-installed-${DISTRO} 26 | args: 27 | - http_proxy 28 | - https_proxy 29 | - no_proxy 30 | depends_on: 31 | - validator 32 | entrypoint: supply-chain-tp -C tcp://validator:4004 33 | 34 | server: 35 | image: supply-server-installed:${ISOLATION_ID} 36 | container_name: supply-server-installed 37 | build: 38 | context: . 39 | dockerfile: server/Dockerfile-installed-${DISTRO} 40 | args: 41 | - http_proxy 42 | - https_proxy 43 | - no_proxy 44 | expose: 45 | - 3000 46 | ports: 47 | - '8020:3000' 48 | depends_on: 49 | - validator 50 | - rethink 51 | environment: 52 | - VALIDATOR_URL=tcp://validator:4004 53 | - DB_HOST=rethink 54 | command: | 55 | bash -c " 56 | set -x && 57 | npm run init && 58 | node index.js 59 | " 60 | 61 | ledger-sync: 62 | image: supply-ledger-sync-installed:${ISOLATION_ID} 63 | container_name: supply-ledger-sync-installed 64 | build: 65 | context: . 66 | dockerfile: ledger_sync/Dockerfile-installed-${DISTRO} 67 | depends_on: 68 | - validator 69 | - rethink 70 | environment: 71 | - VALIDATOR_URL=tcp://validator:4004 72 | - DB_HOST=rethink 73 | 74 | asset-client: 75 | image: supply-asset-client-installed:${ISOLATION_ID} 76 | container_name: supply-asset-client-installed 77 | build: 78 | context: . 79 | dockerfile: asset_client/Dockerfile-installed 80 | expose: 81 | - 80 82 | ports: 83 | - '8021:80' 84 | depends_on: 85 | - server 86 | 87 | fish-client: 88 | image: supply-fish-client-installed:${ISOLATION_ID} 89 | container_name: supply-fish-client-installed 90 | build: 91 | context: . 92 | dockerfile: fish_client/Dockerfile-installed 93 | expose: 94 | - 80 95 | ports: 96 | - '8022:80' 97 | depends_on: 98 | - server 99 | 100 | rethink: 101 | image: rethinkdb 102 | container_name: supply-rethink 103 | expose: 104 | - 8080 105 | - 28015 106 | 107 | validator: 108 | image: hyperledger/sawtooth-validator:1.0 109 | container_name: supply-validator 110 | expose: 111 | - 4004 112 | # start the validator with an empty genesis batch 113 | entrypoint: | 114 | bash -c " 115 | if [ ! -f /etc/sawtooth/keys/validator.priv ]; then 116 | sawadm keygen && 117 | sawtooth keygen my_key && 118 | sawset genesis -k /root/.sawtooth/keys/my_key.priv && 119 | sawadm genesis config-genesis.batch 120 | fi; 121 | sawtooth-validator -v \ 122 | --endpoint tcp://validator:8800 \ 123 | --bind component:tcp://eth0:4004 \ 124 | --bind network:tcp://eth0:8800 125 | " 126 | 127 | settings-tp: 128 | image: hyperledger/sawtooth-settings-tp:1.0 129 | container_name: supply-settings-tp 130 | depends_on: 131 | - validator 132 | entrypoint: settings-tp -v -C tcp://validator:4004 133 | -------------------------------------------------------------------------------- /docs/source/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* Override table cells ignoring width settings when content is too long 2 | */ 3 | .wy-table-responsive table td, .wy-table-responsive table th { 4 | white-space: normal; 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/_templates/indexcontent.html: -------------------------------------------------------------------------------- 1 | {%- extends "layout.html" %} 2 | {% set title = 'Overview' %} 3 | {% block body %} 4 | 5 |

Hyperledger Sawtooth Supply Chain documentation

6 | 7 | 9 | 10 | 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /docs/source/contents.rst: -------------------------------------------------------------------------------- 1 | 2 | Table of Contents 3 | ================= 4 | 5 | .. toctree:: 6 | 7 | family_specification.rst 8 | -------------------------------------------------------------------------------- /docs/supply-chain-build-docs: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds the environment needed to build the Sawtooth Supply Chain docs 18 | # Running the image will put the docs in 19 | # sawtooth-supply-chain/docs/build on your local machine. 20 | # 21 | # Build: 22 | # $ cd sawtooth-supply-chain 23 | # $ docker build . -f docs/supply-chain-build-docs -t supply-chain-build-docs 24 | # 25 | # Run: 26 | # $ cd sawtooth-supply-chain 27 | # $ docker run -v $(pwd):/project/sawtooth-supply-chain supply-chain-build-docs 28 | 29 | FROM ubuntu:bionic 30 | 31 | RUN apt-get update \ 32 | && apt-get install gnupg -y 33 | 34 | RUN echo "deb http://repo.sawtooth.me/ubuntu/ci bionic universe" >> /etc/apt/sources.list \ 35 | && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8AA7AF1F1091A5FD \ 36 | && apt-get update \ 37 | && apt-get install -y -q \ 38 | build-essential \ 39 | git \ 40 | latexmk \ 41 | pep8 \ 42 | python3-colorlog \ 43 | python3-dev \ 44 | python3-pip \ 45 | python3-protobuf \ 46 | && apt-get clean \ 47 | && rm -rf /var/lib/apt/lists/* \ 48 | && pip3 install \ 49 | pylint \ 50 | bandit 51 | 52 | ENV DEBIAN_FRONTEND=noninteractive 53 | 54 | # Install jsdoc 55 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 56 | curl \ 57 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_6.x \ 58 | && chmod 755 /tmp/setup-node.sh \ 59 | && /tmp/setup-node.sh \ 60 | && apt-get install nodejs npm -y -q \ 61 | && rm /tmp/setup-node.sh \ 62 | && apt-get clean \ 63 | && rm -rf /var/lib/apt/lists/* \ 64 | && npm install -g \ 65 | jsdoc 66 | 67 | RUN apt-get update && apt-get install -y -q \ 68 | dvipng \ 69 | make \ 70 | sudo \ 71 | texlive-fonts-recommended \ 72 | texlive-latex-base \ 73 | texlive-latex-extra \ 74 | texlive-latex-recommended \ 75 | zip \ 76 | && apt-get clean \ 77 | && rm -rf /var/lib/apt/lists/* \ 78 | && pip3 install \ 79 | sphinx \ 80 | sphinxcontrib-httpdomain \ 81 | sphinxcontrib-openapi \ 82 | sphinx_rtd_theme 83 | 84 | WORKDIR /project/sawtooth-supply-chain/docs 85 | CMD make html latexpdf 86 | -------------------------------------------------------------------------------- /fish_client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ----------------------------------------------------------------------------- 15 | 16 | # NOTE: Use `volumes` to make: fish_client/public/ 17 | # available at: /usr/local/apache2/htdocs/ 18 | 19 | FROM httpd:2.4 20 | 21 | RUN echo "\ 22 | \n\ 23 | ServerName fish_client\n\ 24 | AddDefaultCharset utf-8\n\ 25 | LoadModule proxy_module modules/mod_proxy.so\n\ 26 | LoadModule proxy_http_module modules/mod_proxy_http.so\n\ 27 | ProxyPass /api http://server:3000\n\ 28 | ProxyPassReverse /api http://server:3000\n\ 29 | \n\ 30 | " >>/usr/local/apache2/conf/httpd.conf 31 | 32 | ENV PATH $PATH:/sawtooth-supply-chain/bin 33 | 34 | EXPOSE 80/tcp 35 | -------------------------------------------------------------------------------- /fish_client/Dockerfile-installed: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ----------------------------------------------------------------------------- 15 | 16 | FROM node:8 AS static_builder 17 | 18 | WORKDIR /supply_chain/fish_client 19 | COPY fish_client/package.json . 20 | RUN npm install 21 | 22 | COPY protos/ ../protos/ 23 | COPY fish_client/ . 24 | RUN npm run build 25 | 26 | FROM httpd:2.4 27 | 28 | COPY --from=static_builder /supply_chain/fish_client/public/ /usr/local/apache2/htdocs/ 29 | 30 | RUN echo "\ 31 | \n\ 32 | ServerName fish_client\n\ 33 | AddDefaultCharset utf-8\n\ 34 | LoadModule proxy_module modules/mod_proxy.so\n\ 35 | LoadModule proxy_http_module modules/mod_proxy_http.so\n\ 36 | ProxyPass /api http://server:3000\n\ 37 | ProxyPassReverse /api http://server:3000\n\ 38 | \n\ 39 | " >>/usr/local/apache2/conf/httpd.conf 40 | 41 | EXPOSE 80/tcp 42 | -------------------------------------------------------------------------------- /fish_client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fish_net", 3 | "version": "0.0.0", 4 | "description": "A Sawtooth Supply Chain client web app for trading fish", 5 | "main": "", 6 | "scripts": { 7 | "generate-proto-files": "node scripts/generate_proto_json.js > src/generated_protos.json", 8 | "watch": "npm run generate-proto-files && webpack-dev-server --env development --hot", 9 | "build": "npm run generate-proto-files && webpack --env production", 10 | "test": "standard src/**/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hyperledger/sawtooth-supply-chain.git" 15 | }, 16 | "author": "", 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/hyperledger/sawtooth-supply-chain/issues" 20 | }, 21 | "homepage": "https://github.com/hyperledger/sawtooth-supply-chain#readme", 22 | "dependencies": { 23 | "bootstrap": "^4.0.0-beta", 24 | "chart.js": "^2.7.0", 25 | "google-maps": "^3.2.1", 26 | "jquery": "^3.2.1", 27 | "lodash": "^4.17.4", 28 | "mithril": "^1.1.3", 29 | "moment": "^2.18.1", 30 | "octicons": "^6.0.1", 31 | "popper.js": "^1.12.3", 32 | "sawtooth-sdk": "^1.0.0-rc", 33 | "sjcl": "^1.0.7" 34 | }, 35 | "devDependencies": { 36 | "autoprefixer": "^7.1.2", 37 | "css-loader": "^0.28.5", 38 | "node-sass": "^4.5.3", 39 | "postcss-loader": "^2.0.6", 40 | "precss": "^2.0.0", 41 | "protobufjs": "^6.8.0", 42 | "sass-loader": "^6.0.6", 43 | "standard": "^10.0.3", 44 | "style-loader": "^0.18.2", 45 | "webpack": "^4.30.0", 46 | "webpack-cli": "^3.3.1", 47 | "webpack-dev-server": ">=3.1.11" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fish_client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FishNet 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /fish_client/sample_data/core_types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "fish", 4 | "properties": [ 5 | { 6 | "name": "species", 7 | "dataType": 4, 8 | "required": true 9 | }, 10 | { 11 | "name": "length", 12 | "dataType": 3, 13 | "required": true, 14 | "numberExponent": -6 15 | }, 16 | { 17 | "name": "weight", 18 | "dataType": 3, 19 | "required": true, 20 | "numberExponent": -6 21 | }, 22 | { 23 | "name": "location", 24 | "dataType": 7, 25 | "required": true 26 | }, 27 | { 28 | "name": "temperature", 29 | "dataType": 3, 30 | "numberExponent": -6 31 | }, 32 | { 33 | "name": "tilt", 34 | "dataType": 4 35 | }, 36 | { 37 | "name": "shock", 38 | "dataType": 4 39 | } 40 | ] 41 | } 42 | ] -------------------------------------------------------------------------------- /fish_client/sample_data/sample_updates.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "recordId": "1-4m-4-f1sh", 4 | "privateKey": "1ab38027228aef9a6642d6acbf7aa62e8db18795cc09c3e03586e49396afb3df", 5 | "updates": [ 6 | { 7 | "name": "temperature", 8 | "dataType": 3, 9 | "value": 500000, 10 | "isRelative": true, 11 | "startValue": -2000000 12 | }, 13 | 14 | { 15 | "name": "location", 16 | "dataType": 7, 17 | "value": { 18 | "latitude": -56740, 19 | "longitude": -99300 20 | }, 21 | "isRelative": true, 22 | "isAverage": true 23 | }, 24 | 25 | { 26 | "name": "tilt", 27 | "dataType": 4, 28 | "value": { 29 | "x": 5000000, 30 | "y": 5000000 31 | }, 32 | "isRelative": true 33 | }, 34 | 35 | { 36 | "name": "shock", 37 | "dataType": 4, 38 | "value": { 39 | "accel": 10000000, 40 | "duration": 500000 41 | }, 42 | "isAlwaysPositive": true, 43 | "noOpChance": 0.9 44 | } 45 | ] 46 | }, 47 | 48 | { 49 | "recordId": "1-4m-sm0l-f1sh", 50 | "privateKey": "1ab38027228aef9a6642d6acbf7aa62e8db18795cc09c3e03586e49396afb3df", 51 | "updates": [ 52 | { 53 | "name": "temperature", 54 | "dataType": 3, 55 | "value": 1000000, 56 | "isRelative": true, 57 | "startValue": 4000000 58 | }, 59 | 60 | { 61 | "name": "location", 62 | "dataType": 7, 63 | "value": { 64 | "latitude": -90360, 65 | "longitude": -61290 66 | }, 67 | "isRelative": true, 68 | "isAverage": true 69 | }, 70 | 71 | { 72 | "name": "tilt", 73 | "dataType": 4, 74 | "value": { 75 | "x": 2500000, 76 | "y": 2500000 77 | }, 78 | "isRelative": true 79 | }, 80 | 81 | { 82 | "name": "shock", 83 | "dataType": 4, 84 | "value": { 85 | "accel": 5000000, 86 | "duration": 500000 87 | }, 88 | "isAlwaysPositive": true, 89 | "noOpChance": 0.95 90 | } 91 | ] 92 | }, 93 | 94 | { 95 | "recordId": "1-4m-n0t-f1sh", 96 | "privateKey": "063f9ca21d4ef4955f3e120374f7c22272f42106c466a91d01779efba22c2cb6", 97 | "updates": [ 98 | { 99 | "name": "temperature", 100 | "dataType": 3, 101 | "value": 3000000, 102 | "isRelative": true, 103 | "startValue": 35000000 104 | }, 105 | 106 | { 107 | "name": "location", 108 | "dataType": 7, 109 | "value": { 110 | "latitude": -215515, 111 | "longitude": -1073695 112 | }, 113 | "isRelative": true, 114 | "isAverage": true 115 | }, 116 | 117 | { 118 | "name": "tilt", 119 | "dataType": 4, 120 | "value": { 121 | "x": 10000000, 122 | "y": 10000000 123 | }, 124 | "isRelative": true 125 | }, 126 | 127 | { 128 | "name": "shock", 129 | "dataType": 4, 130 | "value": { 131 | "accel": 20000000, 132 | "duration": 500000 133 | }, 134 | "isAlwaysPositive": true, 135 | "noOpChance": 0.8 136 | } 137 | ] 138 | } 139 | ] -------------------------------------------------------------------------------- /fish_client/scripts/generate_proto_json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ------------------------------------------------------------------------------ 16 | */ 17 | 'use strict' 18 | 19 | const fs = require('fs') 20 | const path = require('path') 21 | const protobuf = require('protobufjs') 22 | const jsonTarget = require('../node_modules/protobufjs/cli/targets/json') 23 | 24 | let root = new protobuf.Root() 25 | 26 | let files = fs 27 | .readdirSync(path.resolve(__dirname, '../../protos')) 28 | .map(f => path.resolve(__dirname, '../../protos', f)) 29 | .filter(f => f.endsWith('.proto')) 30 | 31 | try { 32 | root = root.loadSync(files) 33 | } catch (e) { 34 | console.log('Unable to load protobuf files!') 35 | throw e 36 | } 37 | 38 | jsonTarget(root, {}, (err, output) => { 39 | if (err) { 40 | throw err 41 | } 42 | 43 | if (output !== '') { 44 | process.stdout.write(output, 'utf8') 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /fish_client/src/components/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const octicons = require('octicons') 22 | 23 | /** 24 | * Returns a header styled to be a page title 25 | */ 26 | const title = title => m('h3.text-center.mb-3', title) 27 | 28 | /** 29 | * Returns a row of any number of columns, suitable for placing in a container 30 | */ 31 | const row = columns => { 32 | if (!_.isArray(columns)) columns = [columns] 33 | return m('.row', columns.map(col => m('.col-md', col))) 34 | } 35 | 36 | /** 37 | * Returns a mithriled icon from Github's octicon set 38 | */ 39 | const icon = name => m.trust(octicons[name].toSVG()) 40 | 41 | module.exports = { 42 | title, 43 | row, 44 | icon 45 | } 46 | -------------------------------------------------------------------------------- /fish_client/src/components/modals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const $ = require('jquery') 22 | 23 | /** 24 | * A basic Bootstrap modal. Requires at least a title and body be set in 25 | * attributes. Also accepts text and functions for accepting/canceling. 26 | */ 27 | const BasicModal = { 28 | view (vnode) { 29 | // Set default accept/cancel values 30 | const acceptText = vnode.attrs.acceptText || 'Accept' 31 | const cancelText = vnode.attrs.cancelText || 'Cancel' 32 | const acceptFn = vnode.attrs.acceptFn || _.identity 33 | const cancelFn = vnode.attrs.cancelFn || _.identity 34 | 35 | return m('.modal.fade#modal', { 36 | tabindex: '-1', 37 | role: 'dialog', 38 | 'aria-labelby': 'modal' 39 | }, [ 40 | m('.modal-dialog', { role: 'document' }, 41 | m('form', 42 | m('.modal-content', 43 | m('.modal-header', 44 | m('h5.modal-title', vnode.attrs.title), 45 | m('button.close', { 46 | type: 'button', 47 | onclick: cancelFn, 48 | 'data-dismiss': 'modal', 49 | 'aria-label': 'Close' 50 | }, m('span', { 'aria-hidden': 'true' }, m.trust('×'))) 51 | ), 52 | m('.modal-body', vnode.attrs.body), 53 | m('.modal-footer', 54 | m('button.btn.btn-secondary', { 55 | type: 'button', 56 | onclick: cancelFn, 57 | 'data-dismiss': 'modal' 58 | }, cancelText), 59 | m('button.btn.btn-primary', { 60 | type: 'submit', 61 | onclick: acceptFn, 62 | 'data-dismiss': 'modal' 63 | }, acceptText))))) 64 | ]) 65 | } 66 | } 67 | 68 | /** 69 | * Renders/shows a modal component, with attributes, returning a promise. 70 | * On close, unmounts the component and resolves/rejects the promise, 71 | * with rejection indicating the cancel button was pressed. 72 | */ 73 | const show = (modal, attrs, children) => { 74 | let acceptFn = null 75 | let cancelFn = null 76 | const onClosePromise = new Promise((resolve, reject) => { 77 | acceptFn = resolve 78 | cancelFn = reject 79 | }) 80 | 81 | const container = document.getElementById('modal-container') 82 | m.render(container, 83 | m(modal, _.assign(attrs, { acceptFn, cancelFn }, children))) 84 | const $modal = $('#modal') 85 | $modal.on('hidden.bs.modal', () => m.mount(container, null)) 86 | $modal.modal('show') 87 | 88 | return onClosePromise 89 | } 90 | 91 | module.exports = { 92 | BasicModal, 93 | show 94 | } 95 | -------------------------------------------------------------------------------- /fish_client/src/components/navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | /** 22 | * A simple navbar wrapper, which displays children in the responsive collapse. 23 | */ 24 | const Navbar = { 25 | view (vnode) { 26 | return m('nav.navbar.navbar-expand-sm.navbar-dark.bg-dark.mb-5', [ 27 | m('a.navbar-brand[href="/"]', { oncreate: m.route.link }, 'FishNet'), 28 | m('button.navbar-toggler', { 29 | type: 'button', 30 | 'data-toggle': 'collapse', 31 | 'data-target': '#navbar', 32 | 'aria-controls': 'navbar', 33 | 'aria-expanded': 'false', 34 | 'aria-label': 'Toggle navigation' 35 | }, m('span.navbar-toggler-icon')), 36 | m('#navbar.collapse.navbar-collapse', vnode.children) 37 | ]) 38 | } 39 | } 40 | 41 | /** 42 | * Creates a list of left-aligned navbar links from href/label tuples. 43 | */ 44 | const links = items => { 45 | return m('ul.navbar-nav.mr-auto', items.map(([href, label]) => { 46 | return m('li.nav-item', [ 47 | m('a.nav-link', { href, oncreate: m.route.link }, label) 48 | ]) 49 | })) 50 | } 51 | 52 | /** 53 | * Creates a single link for use in a navbar. 54 | */ 55 | const link = (href, label) => { 56 | return m('.navbar-nav', [ 57 | m('a.nav-link', { href, oncreate: m.route.link }, label) 58 | ]) 59 | } 60 | 61 | /** 62 | * Creates a navigation button styled for use in the navbar. 63 | */ 64 | const button = (href, label) => { 65 | return m('a.btn.btn-outline-primary.my-2.my-sm-0', { 66 | href, 67 | oncreate: m.route.link 68 | }, label) 69 | } 70 | 71 | module.exports = { 72 | Navbar, 73 | link, 74 | links, 75 | button 76 | } 77 | -------------------------------------------------------------------------------- /fish_client/src/components/tables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const Table = { 22 | oninit (vnode) { 23 | if (!vnode.attrs.noRowsText) { 24 | vnode.attrs.noRowsText = 'No rows available' 25 | } 26 | }, 27 | 28 | view (vnode) { 29 | return [ 30 | m('table.table', 31 | m('thead', 32 | m('tr', 33 | vnode.attrs.headers.map((header) => m('th', header)))), 34 | m('tbody', 35 | vnode.attrs.rows.length > 0 36 | ? vnode.attrs.rows 37 | .map((row) => 38 | m('tr', 39 | row.map((col) => m('td', col)))) 40 | : m('tr', 41 | m('td.text-center', {colSpan: 5}, 42 | vnode.attrs.noRowsText)) 43 | ) 44 | ) 45 | ] 46 | } 47 | } 48 | 49 | const FilterGroup = { 50 | oninit (vnode) { 51 | vnode.state.currentFilter = vnode.attrs.initialFilter 52 | }, 53 | 54 | view (vnode) { 55 | return [ 56 | m('.btn-group', { 57 | role: 'group', 58 | 'aria-label': vnode.attrs.ariaLabel 59 | }, 60 | Object.entries(vnode.attrs.filters).map(([label, action]) => 61 | m('button.btn', { 62 | className: vnode.state.currentFilter === label ? 'btn-primary' : 'btn-light', 63 | onclick: (e) => { 64 | e.preventDefault() 65 | vnode.state.currentFilter = label 66 | action() 67 | } 68 | }, label))) 69 | ] 70 | } 71 | } 72 | 73 | const PagingButtons = { 74 | view (vnode) { 75 | return [ 76 | m('.d-flex.justify-content-end', 77 | m('.btn-group', { 78 | role: 'group', 79 | 'aria-label': 'Paging controls' 80 | }, 81 | m('button.btn.btn-light', { 82 | onclick: (e) => { 83 | e.preventDefault() 84 | vnode.attrs.setPage(Math.max(0, vnode.attrs.currentPage - 1)) 85 | }, 86 | disabled: vnode.attrs.currentPage === 0 87 | }, '\u25c0'), // right arrow 88 | m('button.btn.btn-light', { 89 | onclick: (e) => { 90 | e.preventDefault() 91 | vnode.attrs.setPage( 92 | Math.min(vnode.attrs.maxPage, vnode.attrs.currentPage + 1)) 93 | }, 94 | disabled: (vnode.attrs.currentPage === vnode.attrs.maxPage) 95 | }, '\u25b6'))) // left arrow 96 | ] 97 | } 98 | } 99 | 100 | module.exports = { 101 | Table, 102 | FilterGroup, 103 | PagingButtons 104 | } 105 | -------------------------------------------------------------------------------- /fish_client/src/services/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | const sjcl = require('sjcl') 22 | 23 | const API_PATH = 'api/' 24 | const STORAGE_KEY = 'fish_net.authorization' 25 | let authToken = null 26 | 27 | /** 28 | * Generates a base-64 encoded SHA-256 hash of a plain text password 29 | * for submission to authorization routes 30 | */ 31 | const hashPassword = password => { 32 | const bits = sjcl.hash.sha256.hash(password) 33 | return sjcl.codec.base64.fromBits(bits) 34 | } 35 | 36 | /** 37 | * Getters and setters to handle the auth token both in memory and storage 38 | */ 39 | const getAuth = () => { 40 | if (!authToken) { 41 | authToken = window.localStorage.getItem(STORAGE_KEY) 42 | } 43 | return authToken 44 | } 45 | 46 | const setAuth = token => { 47 | window.localStorage.setItem(STORAGE_KEY, token) 48 | authToken = token 49 | return authToken 50 | } 51 | 52 | const clearAuth = () => { 53 | const token = getAuth() 54 | window.localStorage.clear(STORAGE_KEY) 55 | authToken = null 56 | return token 57 | } 58 | 59 | /** 60 | * Parses the authToken to return the logged in user's public key 61 | */ 62 | const getPublicKey = () => { 63 | const token = getAuth() 64 | if (!token) return null 65 | return window.atob(token.split('.')[1]) 66 | } 67 | 68 | // Adds Authorization header and prepends API path to url 69 | const baseRequest = opts => { 70 | const Authorization = getAuth() 71 | const authHeader = Authorization ? { Authorization } : {} 72 | opts.headers = _.assign(opts.headers, authHeader) 73 | opts.url = API_PATH + opts.url 74 | return m.request(opts) 75 | } 76 | 77 | /** 78 | * Submits a request to an api endpoint with an auth header if present 79 | */ 80 | const request = (method, endpoint, data) => { 81 | return baseRequest({ 82 | method, 83 | url: endpoint, 84 | data 85 | }) 86 | } 87 | 88 | /** 89 | * Method specific versions of request 90 | */ 91 | const get = _.partial(request, 'GET') 92 | const post = _.partial(request, 'POST') 93 | const patch = _.partial(request, 'PATCH') 94 | 95 | /** 96 | * Method for posting a binary file to the API 97 | */ 98 | const postBinary = (endpoint, data) => { 99 | return baseRequest({ 100 | method: 'POST', 101 | url: endpoint, 102 | headers: { 'Content-Type': 'application/octet-stream' }, 103 | // prevent Mithril from trying to JSON stringify the body 104 | serialize: x => x, 105 | data 106 | }) 107 | } 108 | 109 | module.exports = { 110 | hashPassword, 111 | getAuth, 112 | setAuth, 113 | clearAuth, 114 | getPublicKey, 115 | request, 116 | get, 117 | post, 118 | patch, 119 | postBinary 120 | } 121 | -------------------------------------------------------------------------------- /fish_client/src/services/parsing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const moment = require('moment') 21 | const { FLOAT_PRECISION } = require('./payloads') 22 | 23 | const STRINGIFIERS = { 24 | LOCATION: v => `${v.latitude}, ${v.longitude}`, 25 | tilt: v => `X: ${v.x}, Y: ${v.y}`, 26 | shock: v => `Accel: ${v.accel}, Duration: ${v.duration}`, 27 | '*': v => JSON.stringify(v, null, 1).replace(/[{}"]/g, '') 28 | } 29 | 30 | /** 31 | * Parses a property value by its name or type, returning a string for display 32 | */ 33 | const stringifyValue = (value, type, name) => { 34 | if (STRINGIFIERS[type]) { 35 | return STRINGIFIERS[type](value) 36 | } 37 | if (STRINGIFIERS[name]) { 38 | return STRINGIFIERS[name](value) 39 | } 40 | return STRINGIFIERS['*'](value) 41 | } 42 | 43 | /** 44 | * Simple functions that turn numbers or number-like strings to 45 | * an integer (in millionths) or back to a float. 46 | */ 47 | const toFloat = num => parseInt(num) / FLOAT_PRECISION 48 | const toInt = num => parseInt(parseFloat(num) * FLOAT_PRECISION) 49 | 50 | /** 51 | * Calls toFloat on a property value, or it's sub-values in the case of 52 | * location, tilt, or shock 53 | */ 54 | const floatifyValue = value => { 55 | if (_.isString(value)) value = JSON.parse(value) 56 | if (_.isObject(value)) return _.mapValues(value, toFloat) 57 | return toFloat(value) 58 | } 59 | 60 | /** 61 | * Parses seconds into a date/time string 62 | */ 63 | const formatTimestamp = sec => { 64 | if (!sec) { 65 | sec = Date.now() / 1000 66 | } 67 | return moment.unix(sec).format('MM/DD/YYYY, h:mm:ss a') 68 | } 69 | 70 | module.exports = { 71 | toInt, 72 | toFloat, 73 | stringifyValue, 74 | floatifyValue, 75 | formatTimestamp 76 | } 77 | -------------------------------------------------------------------------------- /fish_client/src/utils/records.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _getProp = (record, propName) => { 20 | return record.properties.find((prop) => prop.name === propName) 21 | } 22 | 23 | const getPropertyValue = (record, propName, defaultValue = null) => { 24 | let prop = _getProp(record, propName) 25 | if (prop && prop.value) { 26 | return prop.value 27 | } else { 28 | return defaultValue 29 | } 30 | } 31 | 32 | const isReporter = (record, propName, publicKey) => { 33 | let prop = _getProp(record, propName) 34 | if (prop) { 35 | return prop.reporters.indexOf(publicKey) > -1 36 | } else { 37 | return false 38 | } 39 | } 40 | 41 | const _getPropTimeByComparison = (compare) => (record) => { 42 | if (!record.updates.properties) { 43 | return null 44 | } 45 | 46 | return Object.values(record.updates.properties) 47 | .reduce((acc, updates) => acc.concat(updates), []) 48 | .reduce((selected, update) => 49 | compare(selected.timestamp, update.timestamp) ? update : selected) 50 | .timestamp 51 | } 52 | 53 | const getLatestPropertyUpdateTime = 54 | _getPropTimeByComparison((selected, timestamp) => selected < timestamp) 55 | 56 | const getOldestPropertyUpdateTime = 57 | _getPropTimeByComparison((selected, timestamp) => selected > timestamp) 58 | 59 | const countPropertyUpdates = (record) => { 60 | if (!record.updates.properties) { 61 | return 0 62 | } 63 | 64 | return Object.values(record.updates.properties).reduce( 65 | (sum, updates) => sum + updates.length, 0) 66 | } 67 | 68 | module.exports = { 69 | getPropertyValue, 70 | isReporter, 71 | getLatestPropertyUpdateTime, 72 | getOldestPropertyUpdateTime, 73 | countPropertyUpdates 74 | } 75 | -------------------------------------------------------------------------------- /fish_client/src/views/dashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const Dashboard = { 22 | view (vnode) { 23 | return [ 24 | m('.header.text-center.mb-4', 25 | m('h4', 'Welcome To'), 26 | m('h1.mb-3', 'FishNet'), 27 | m('h5', 28 | m('em', 29 | 'Powered by ', 30 | m('strong', 'Sawtooth Supply Chain')))), 31 | m('.blurb', 32 | m('p', 33 | m('em', 'Sawtooth Supply Chain'), 34 | ' is a general purpose supply chain solution built using the ', 35 | 'power of ', 36 | m('a[href="https://github.com/hyperledger/sawtooth-core"]', 37 | { target: '_blank' }, 38 | "Hyperledger Sawtooth's"), 39 | ' blockchain technology. It maintains a distributed ledger ', 40 | 'that tracks both asset provenance and a timestamped history ', 41 | 'detailing how an asset was stored, handled, and transported.'), 42 | m('p', 43 | m('em', 'FishNet'), 44 | ' demonstrates this unique technology with an illustrative ', 45 | 'example: tracking the provenance of fish from catch to plate. ', 46 | 'One day an application like this could be used by restaurants, ', 47 | 'grocery stores, and their customers to ensure the fish they ', 48 | 'purchase is ethically sourced and properly transported.'), 49 | m('p', 50 | 'To use ', 51 | m('em', 'FishNet'), 52 | ', create an account using the link in the navbar above. ', 53 | 'Once logged in, you will be able to add new fish assets to ', 54 | 'the blockchain and track them with data like temperature or ', 55 | 'location. You will be able to authorize other "agents" on the ', 56 | 'blockchain to track this data as well, or even transfer ', 57 | 'ownership or possession of the fish entirely. For the ', 58 | 'adventurous, these actions can also be accomplished directly ', 59 | 'with the REST API running on the ', 60 | m('em', 'Supply Chain'), 61 | ' server, perfect for automated IoT sensors.')) 62 | ] 63 | } 64 | } 65 | 66 | module.exports = Dashboard 67 | -------------------------------------------------------------------------------- /fish_client/src/views/list_agents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const sortBy = require('lodash/sortBy') 21 | const truncate = require('lodash/truncate') 22 | const {Table, FilterGroup, PagingButtons} = require('../components/tables.js') 23 | const api = require('../services/api') 24 | 25 | const PAGE_SIZE = 50 26 | 27 | const AgentList = { 28 | oninit (vnode) { 29 | vnode.state.agents = [] 30 | vnode.state.filteredAgents = [] 31 | vnode.state.currentPage = 0 32 | 33 | const refresh = () => { 34 | api.get('agents').then((agents) => { 35 | vnode.state.agents = sortBy(agents, 'name') 36 | vnode.state.filteredAgents = vnode.state.agents 37 | }) 38 | .then(() => { vnode.state.refreshId = setTimeout(refresh, 2000) }) 39 | } 40 | 41 | refresh() 42 | }, 43 | 44 | onbeforeremove (vnode) { 45 | clearTimeout(vnode.state.refreshId) 46 | }, 47 | 48 | view (vnode) { 49 | let publicKey = api.getPublicKey() 50 | return [ 51 | m('.agent-list', 52 | m('.row.btn-row.mb-2', _controlButtons(vnode, publicKey)), 53 | m(Table, { 54 | headers: [ 55 | 'Name', 56 | 'Key', 57 | 'Owns', 58 | 'Custodian', 59 | 'Reports' 60 | ], 61 | rows: vnode.state.filteredAgents.slice( 62 | vnode.state.currentPage * PAGE_SIZE, 63 | (vnode.state.currentPage + 1) * PAGE_SIZE) 64 | .map((agent) => [ 65 | m(`a[href=/agents/${agent.key}]`, { oncreate: m.route.link }, 66 | truncate(agent.name, { length: 32 })), 67 | truncate(agent.key, { length: 32 }), 68 | agent.owns.length, 69 | agent.custodian.length, 70 | agent.reports.length 71 | ]), 72 | noRowsText: 'No agents found' 73 | }) 74 | ) 75 | ] 76 | } 77 | } 78 | 79 | const _controlButtons = (vnode, publicKey) => { 80 | if (publicKey) { 81 | let filterAgents = (f) => { 82 | vnode.state.filteredAgents = vnode.state.agents.filter(f) 83 | } 84 | 85 | return [ 86 | m('.col-sm-8', 87 | m(FilterGroup, { 88 | ariaLabel: 'Filter Based on Ownership', 89 | filters: { 90 | 'All': () => { vnode.state.filteredAgents = vnode.state.agents }, 91 | 'Owners': () => filterAgents(agent => agent.owns.length > 0), 92 | 'Custodians': () => filterAgents(agent => agent.custodian.length > 0), 93 | 'Reporters': () => filterAgents(agent => agent.reports.length > 0) 94 | }, 95 | initialFilter: 'All' 96 | })), 97 | m('.col-sm-4', _pagingButtons(vnode)) 98 | ] 99 | } else { 100 | return [ 101 | m('.col-sm-4.ml-auto', _pagingButtons(vnode)) 102 | ] 103 | } 104 | } 105 | 106 | const _pagingButtons = (vnode) => 107 | m(PagingButtons, { 108 | setPage: (page) => { vnode.state.currentPage = page }, 109 | currentPage: vnode.state.currentPage, 110 | maxPage: Math.floor(vnode.state.filteredAgents.length / PAGE_SIZE) 111 | }) 112 | 113 | module.exports = AgentList 114 | -------------------------------------------------------------------------------- /fish_client/src/views/login_form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | 21 | const api = require('../services/api') 22 | const transactions = require('../services/transactions') 23 | const forms = require('../components/forms') 24 | 25 | /** 26 | * The Form for authorizing an existing user. 27 | */ 28 | const LoginForm = { 29 | view (vnode) { 30 | const setter = forms.stateSetter(vnode.state) 31 | 32 | return m('.login-form', [ 33 | m('form', { 34 | onsubmit: (e) => { 35 | e.preventDefault() 36 | const credentials = { 37 | username: vnode.state.username, 38 | password: api.hashPassword(vnode.state.password) 39 | } 40 | api.post('authorization', credentials) 41 | .then(res => { 42 | api.setAuth(res.authorization) 43 | transactions.setPrivateKey(vnode.state.password, 44 | res.encryptedKey) 45 | m.route.set('/') 46 | }) 47 | } 48 | }, 49 | m('legend', 'Login Agent'), 50 | forms.textInput(setter('username'), 'Username'), 51 | forms.passwordInput(setter('password'), 'Password'), 52 | m('.container.text-center', 53 | 'Or you can ', 54 | m('a[href="/signup"]', 55 | { oncreate: m.route.link }, 56 | 'create a new Agent')), 57 | m('.form-group', 58 | m('.row.justify-content-end.align-items-end', 59 | m('col-2', 60 | m('button.btn.btn-primary', 61 | {'data-toggle': 'modal', 'data-target': '#modal'}, 62 | 'Login'))))) 63 | ]) 64 | } 65 | } 66 | 67 | module.exports = LoginForm 68 | -------------------------------------------------------------------------------- /fish_client/src/views/signup_form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const m = require('mithril') 20 | const _ = require('lodash') 21 | 22 | const forms = require('../components/forms') 23 | const api = require('../services/api') 24 | const transactions = require('../services/transactions') 25 | const payloads = require('../services/payloads') 26 | 27 | const passwordCard = state => { 28 | const setter = forms.stateSetter(state) 29 | const validator = forms.validator( 30 | () => state.password === state.confirm, 31 | 'Passwords do not match', 32 | 'confirm' 33 | ) 34 | const passwordField = (id, placeholder) => { 35 | return forms.field( 36 | // Run both state setting and validation on value changes 37 | _.flow(setter(id), validator), 38 | { 39 | id, 40 | placeholder, 41 | type: 'password', 42 | class: 'border-warning' 43 | } 44 | ) 45 | } 46 | 47 | return forms.group('Password', [ 48 | m('.card.text-center.border-warning', 49 | m('.card-header.text-white.bg-warning', m('em', m('strong', 'WARNING!'))), 50 | m('.card-body.text-warning.bg-light', 51 | m('p.card-text', 52 | 'This password will be used as a secret key to encrypt important ', 53 | 'account information. Although it can be changed later, ', 54 | m('em', 55 | 'if lost or forgotten it will be ', 56 | m('strong', 'impossible'), 57 | ' to recover your account.')), 58 | m('p.card-text', 'Keep it secure.'), 59 | passwordField('password', 'Enter password...'), 60 | passwordField('confirm', 'Confirm password...'))) 61 | ]) 62 | } 63 | 64 | const userSubmitter = state => e => { 65 | e.preventDefault() 66 | 67 | const keys = transactions.makePrivateKey(state.password) 68 | const user = _.assign(keys, _.pick(state, 'username', 'email')) 69 | user.password = api.hashPassword(state.password) 70 | const agent = payloads.createAgent(_.pick(state, 'name')) 71 | 72 | transactions.submit(agent, true) 73 | .then(() => api.post('users', user)) 74 | .then(res => api.setAuth(res.authorization)) 75 | .then(() => m.route.set('/')) 76 | } 77 | 78 | /** 79 | * The Form for authorizing an existing user. 80 | */ 81 | const SignupForm = { 82 | view (vnode) { 83 | const setter = forms.stateSetter(vnode.state) 84 | 85 | return m('.signup-form', [ 86 | m('form', { onsubmit: userSubmitter(vnode.state) }, 87 | m('legend', 'Create Agent'), 88 | forms.textInput(setter('name'), 'Name'), 89 | forms.emailInput(setter('email'), 'Email'), 90 | forms.textInput(setter('username'), 'Username'), 91 | passwordCard(vnode.state), 92 | m('.container.text-center', 93 | 'Or you can ', 94 | m('a[href="/login"]', 95 | { oncreate: m.route.link }, 96 | 'login an existing Agent')), 97 | m('.form-group', 98 | m('.row.justify-content-end.align-items-end', 99 | m('col-2', 100 | m('button.btn.btn-primary', 101 | 'Create Agent'))))) 102 | ]) 103 | } 104 | } 105 | 106 | module.exports = SignupForm 107 | -------------------------------------------------------------------------------- /fish_client/styles/main.scss: -------------------------------------------------------------------------------- 1 | // Styling for Google Maps API 2 | #map-container { 3 | height: 400px; 4 | width: 100%; 5 | } 6 | 7 | @import "~bootstrap/scss/bootstrap"; 8 | @import "~octicons/build/octicons.min"; 9 | -------------------------------------------------------------------------------- /fish_client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main', 6 | 7 | output: { 8 | path: path.resolve(__dirname, 'public/dist'), 9 | filename: 'bundle.js' 10 | }, 11 | 12 | module: { 13 | rules: [{ 14 | test: /\.(scss)$/, 15 | use: [{ 16 | loader: 'style-loader' 17 | }, { 18 | loader: 'css-loader' 19 | }, { 20 | loader: 'postcss-loader', 21 | options: { 22 | plugins: () => [ 23 | require('precss'), 24 | require('autoprefixer') 25 | ] 26 | } 27 | }, { 28 | loader: 'sass-loader' 29 | }] 30 | }] 31 | }, 32 | 33 | plugins: [ 34 | new webpack.ProvidePlugin({ 35 | $: 'jquery', 36 | jQuery: 'jquery', 37 | 'window.jQuery': 'jquery', 38 | Popper: ['popper.js', 'default'] 39 | }) 40 | ], 41 | 42 | devServer: { 43 | port: 3001, 44 | contentBase: path.join(__dirname, 'public'), 45 | publicPath: '/dist/', 46 | proxy: { 47 | '/api': 'http://localhost:3000' 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /images/rookies16-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-archives/sawtooth-supply-chain/0311a2d62322adf4c2e4baf40f0b4242fa9b9091/images/rookies16-small.png -------------------------------------------------------------------------------- /images/sawtooth_logo_light_blue-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-archives/sawtooth-supply-chain/0311a2d62322adf4c2e4baf40f0b4242fa9b9091/images/sawtooth_logo_light_blue-small.png -------------------------------------------------------------------------------- /integration/sawtooth_integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-archives/sawtooth-supply-chain/0311a2d62322adf4c2e4baf40f0b4242fa9b9091/integration/sawtooth_integration/__init__.py -------------------------------------------------------------------------------- /integration/sawtooth_integration/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-archives/sawtooth-supply-chain/0311a2d62322adf4c2e4baf40f0b4242fa9b9091/integration/sawtooth_integration/tests/__init__.py -------------------------------------------------------------------------------- /ledger_sync/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:bionic 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | # Install Node and Ubuntu dependencies 25 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 26 | curl \ 27 | ca-certificates \ 28 | gnupg \ 29 | pkg-config \ 30 | build-essential \ 31 | libzmq3-dev \ 32 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 33 | && chmod 755 /tmp/setup-node.sh \ 34 | && /tmp/setup-node.sh \ 35 | && apt-get install nodejs -y -q \ 36 | && rm /tmp/setup-node.sh \ 37 | && apt-get clean \ 38 | && rm -rf /var/lib/apt/lists/* \ 39 | && npm install -g prebuild-install 40 | 41 | WORKDIR /sawtooth-supply-chain/ledger_sync 42 | 43 | RUN \ 44 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 45 | http_proxy=$HTTP_PROXY; \ 46 | fi; \ 47 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 48 | https_proxy=$HTTPS_PROXY; \ 49 | fi; \ 50 | if [ ! -z $http_proxy ]; then \ 51 | npm config set proxy $http_proxy; \ 52 | fi; \ 53 | if [ ! -z $https_proxy ]; then \ 54 | npm config set https-proxy $https_proxy; \ 55 | fi 56 | 57 | 58 | COPY ledger_sync/package.json . 59 | RUN npm install 60 | 61 | CMD ["/usr/bin/node", "index.js"] 62 | -------------------------------------------------------------------------------- /ledger_sync/Dockerfile-installed-bionic: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:bionic 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | RUN apt-get update \ 25 | && apt-get install gnupg -y 26 | 27 | # Install Node and Ubuntu dependencies 28 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 29 | curl \ 30 | ca-certificates \ 31 | pkg-config \ 32 | build-essential \ 33 | libzmq3-dev \ 34 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 35 | && chmod 755 /tmp/setup-node.sh \ 36 | && /tmp/setup-node.sh \ 37 | && apt-get install nodejs -y -q \ 38 | && rm /tmp/setup-node.sh \ 39 | && apt-get clean \ 40 | && rm -rf /var/lib/apt/lists/* \ 41 | && npm install -g prebuild-install 42 | 43 | WORKDIR /sawtooth-supply-chain/ledger_sync 44 | 45 | COPY ledger_sync/package.json . 46 | RUN npm install 47 | 48 | COPY protos/ ../protos/ 49 | COPY ledger_sync/ . 50 | 51 | EXPOSE 3000/tcp 52 | 53 | CMD ["/usr/bin/node", "index.js"] 54 | -------------------------------------------------------------------------------- /ledger_sync/Dockerfile-installed-xenial: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:xenial 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | # Install Node and Ubuntu dependencies 25 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 26 | curl \ 27 | ca-certificates \ 28 | pkg-config \ 29 | build-essential \ 30 | libzmq3-dev \ 31 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 32 | && chmod 755 /tmp/setup-node.sh \ 33 | && /tmp/setup-node.sh \ 34 | && apt-get install nodejs -y -q \ 35 | && rm /tmp/setup-node.sh \ 36 | && apt-get clean \ 37 | && rm -rf /var/lib/apt/lists/* \ 38 | && npm install -g prebuild-install 39 | 40 | WORKDIR /sawtooth-supply-chain/ledger_sync 41 | 42 | COPY ledger_sync/package.json . 43 | RUN npm install 44 | 45 | COPY protos/ ../protos/ 46 | COPY ledger_sync/ . 47 | 48 | EXPOSE 3000/tcp 49 | 50 | CMD ["/usr/bin/node", "index.js"] 51 | -------------------------------------------------------------------------------- /ledger_sync/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "RETRY_WAIT": 5000, 4 | "VALIDATOR_URL": "tcp://localhost:4004", 5 | "DB_HOST": "localhost", 6 | "DB_PORT": 28015, 7 | "DB_NAME": "supply_chain" 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ledger_sync/db/blocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const r = require('rethinkdb') 20 | const db = require('./') 21 | 22 | const stateTables = [ 23 | 'agents', 24 | 'records', 25 | 'recordTypes', 26 | 'properties', 27 | 'propertyPages', 28 | 'proposals' 29 | ] 30 | 31 | const getForkedDocRemover = blockNum => tableName => { 32 | return db.modifyTable(tableName, table => { 33 | return table 34 | .filter(r.row('startBlockNum').ge(blockNum)) 35 | .delete() 36 | .do(() => table.filter(doc => doc('endBlockNum').ge(blockNum))) 37 | .update({endBlockNum: Number.MAX_SAFE_INTEGER}) 38 | }) 39 | } 40 | 41 | const resolveFork = block => { 42 | const defork = getForkedDocRemover(block.blockNum) 43 | return db.modifyTable('blocks', blocks => { 44 | return blocks 45 | .filter(r.row('blockNum').ge(block.blockNum)) 46 | .delete() 47 | .do(() => blocks.insert(block)) 48 | }) 49 | .then(() => Promise.all(stateTables.map(tableName => defork(tableName)))) 50 | .then(() => block) 51 | } 52 | 53 | const insert = block => { 54 | return db.modifyTable('blocks', blocks => { 55 | return blocks 56 | .get(block.blockNum) 57 | .do(foundBlock => { 58 | return r.branch(foundBlock, foundBlock, blocks.insert(block)) 59 | }) 60 | }) 61 | .then(result => { 62 | // If the blockNum did not already exist, or had the same id 63 | // there is no fork, return the block 64 | if (!result.blockId || result.blockId === block.blockId) { 65 | return block 66 | } 67 | return resolveFork(block) 68 | }) 69 | } 70 | 71 | module.exports = { 72 | insert 73 | } 74 | -------------------------------------------------------------------------------- /ledger_sync/db/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const r = require('rethinkdb') 20 | const config = require('../system/config') 21 | 22 | const HOST = config.DB_HOST 23 | const PORT = config.DB_PORT 24 | const NAME = config.DB_NAME 25 | const RETRY_WAIT = config.RETRY_WAIT 26 | const AWAIT_TABLE = 'blocks' 27 | 28 | // Connection to db for query methods, run connect before querying 29 | let connection = null 30 | 31 | const promisedTimeout = (fn, wait) => { 32 | return new Promise(resolve => setTimeout(resolve, wait)).then(fn); 33 | } 34 | 35 | const awaitDatabase = () => { 36 | return r.tableList().run(connection) 37 | .then(tableNames => { 38 | if (!tableNames.includes(AWAIT_TABLE)) { 39 | throw new Error() 40 | } 41 | console.log('Successfully connected to database:', NAME) 42 | }) 43 | .catch(() => { 44 | console.warn('Database not initialized:', NAME) 45 | console.warn(`Retrying database in ${RETRY_WAIT / 1000} seconds...`) 46 | return promisedTimeout(awaitDatabase, RETRY_WAIT) 47 | }) 48 | } 49 | 50 | const connect = () => { 51 | return r.connect({host: HOST, port: PORT, db: NAME}) 52 | .then(conn => { 53 | connection = conn 54 | return awaitDatabase() 55 | }) 56 | .catch(err => { 57 | if (err instanceof r.Error.ReqlDriverError) { 58 | console.warn('Unable to connect to RethinkDB') 59 | console.warn(`Retrying in ${RETRY_WAIT / 1000} seconds...`) 60 | return promisedTimeout(connect, RETRY_WAIT) 61 | } 62 | throw err 63 | }) 64 | } 65 | 66 | // Runs a specified query against a database table 67 | const queryTable = (table, query, removeCursor = true) => { 68 | return query(r.table(table)) 69 | .run(connection) 70 | .then(cursor => removeCursor ? cursor.toArray() : cursor) 71 | .catch(err => { 72 | console.error(`Unable to query "${table}" table!`) 73 | console.error(err.message) 74 | throw new Error(err.message) 75 | }) 76 | } 77 | 78 | // Use for queries that modify a table, turns error messages into errors 79 | const modifyTable = (table, query) => { 80 | return queryTable(table, query, false) 81 | .then(results => { 82 | if (!results) { 83 | throw new Error(`Unknown error while attempting to modify "${table}"`) 84 | } 85 | if (results.errors > 0) { 86 | throw new Error(results.first_error) 87 | } 88 | return results 89 | }) 90 | } 91 | 92 | module.exports = { 93 | connect, 94 | queryTable, 95 | modifyTable 96 | } 97 | -------------------------------------------------------------------------------- /ledger_sync/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const db = require('./db') 20 | const subscriber = require('./subscriber') 21 | const protos = require('./subscriber/protos') 22 | 23 | Promise.all([ 24 | db.connect(), 25 | protos.compile() 26 | ]) 27 | .then(subscriber.start) 28 | .catch(err => console.error(err.message)) 29 | -------------------------------------------------------------------------------- /ledger_sync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supply_chain_ledger_sync", 3 | "version": "0.0.0", 4 | "description": "A simple component to sync blockchain state to a database", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "watch": "nodemon index.js", 12 | "test": "standard" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/hyperledger/sawtooth-supply-chain.git" 17 | }, 18 | "author": "", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/hyperledger/sawtooth-supply-chain/issues" 22 | }, 23 | "homepage": "https://github.com/hyperledger/sawtooth-supply-chain#readme", 24 | "dependencies": { 25 | "body-parser": "^1.17.2", 26 | "js-schema": "^1.0.1", 27 | "lodash": "^4.17.4", 28 | "protobufjs": "^6.8.0", 29 | "rethinkdb": "^2.3.3", 30 | "sawtooth-sdk": "^1.0.0-rc" 31 | }, 32 | "devDependencies": { 33 | "nodemon": "^1.11.0", 34 | "standard": "^10.0.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ledger_sync/subscriber/deltas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const blocks = require('../db/blocks') 21 | const state = require('../db/state') 22 | const protos = require('./protos') 23 | 24 | const deltaQueue = { 25 | _queue: [], 26 | _running: false, 27 | 28 | add (promisedFn) { 29 | this._queue.push(promisedFn) 30 | this._runUntilEmpty() 31 | }, 32 | 33 | _runUntilEmpty () { 34 | if (this._running) return 35 | this._running = true 36 | this._runNext() 37 | }, 38 | 39 | _runNext () { 40 | if (this._queue.length === 0) { 41 | this._running = false 42 | } else { 43 | const current = this._queue.shift() 44 | return current().then(() => this._runNext()) 45 | } 46 | } 47 | } 48 | 49 | const getProtoName = address => { 50 | const typePrefix = address.slice(6, 8) 51 | if (typePrefix === 'ea') { 52 | if (address.slice(-4) === '0000') return 'Property' 53 | else return 'PropertyPage' 54 | } 55 | 56 | const names = { 57 | ae: 'Agent', 58 | aa: 'Proposal', 59 | ec: 'Record', 60 | ee: 'RecordType' 61 | } 62 | if (names[typePrefix]) return names[typePrefix] 63 | 64 | throw new Error(`Blockchain Error: No Protobuf for prefix "${typePrefix}"`) 65 | } 66 | 67 | const getObjectifier = address => { 68 | const name = getProtoName(address) 69 | return stateInstance => { 70 | const obj = protos[name].toObject(stateInstance, { 71 | enums: String, // use string names for enums 72 | longs: Number, // convert int64 to Number, limiting precision to 2^53 73 | defaults: true // use default for falsey values 74 | }) 75 | if (name === 'PropertyPage') { 76 | obj.pageNum = parseInt(address.slice(-4), 16) 77 | } 78 | return obj 79 | } 80 | } 81 | 82 | const stateAdder = address => { 83 | const addState = state[`add${getProtoName(address)}`] 84 | const toObject = getObjectifier(address) 85 | return (stateInstance, blockNum) => { 86 | addState(toObject(stateInstance), blockNum) 87 | } 88 | } 89 | 90 | const getEntries = ({ address, value }) => { 91 | return protos[`${getProtoName(address)}Container`] 92 | .decode(value) 93 | .entries 94 | } 95 | 96 | const entryAdder = block => change => { 97 | const addState = stateAdder(change.address) 98 | return Promise.all(getEntries(change).map(entry => { 99 | return addState(entry, block.blockNum) 100 | })) 101 | } 102 | 103 | const handle = (block, changes) => { 104 | deltaQueue.add(() => { 105 | const [ pageChanges, otherChanges ] = _.partition(changes, change => { 106 | return getProtoName(change.address) === 'PropertyPage' 107 | }) 108 | 109 | return Promise.all(otherChanges.map(entryAdder(block))) 110 | .then(() => { 111 | // If there are page changes, give other changes a chance to propagate 112 | const wait = pageChanges.length === 0 ? 0 : 100 113 | return new Promise(resolve => setTimeout(resolve, wait)) 114 | }) 115 | .then(() => Promise.all(pageChanges.map(entryAdder(block)))) 116 | .then(() => blocks.insert(block)) 117 | }) 118 | } 119 | 120 | module.exports = { 121 | handle 122 | } 123 | -------------------------------------------------------------------------------- /ledger_sync/subscriber/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const { Stream } = require('sawtooth-sdk/messaging/stream') 21 | const { 22 | Message, 23 | EventList, 24 | EventSubscription, 25 | EventFilter, 26 | StateChangeList, 27 | ClientEventsSubscribeRequest, 28 | ClientEventsSubscribeResponse 29 | } = require('sawtooth-sdk/protobuf') 30 | 31 | const deltas = require('./deltas') 32 | const config = require('../system/config') 33 | 34 | const PREFIX = '3400de' 35 | const NULL_BLOCK_ID = '0000000000000000' 36 | const VALIDATOR_URL = config.VALIDATOR_URL 37 | const stream = new Stream(VALIDATOR_URL) 38 | 39 | // Parse Block Commit Event 40 | const getBlock = events => { 41 | const block = _.chain(events) 42 | .find(e => e.eventType === 'sawtooth/block-commit') 43 | .get('attributes') 44 | .map(a => [a.key, a.value]) 45 | .fromPairs() 46 | .value() 47 | 48 | return { 49 | blockNum: parseInt(block.block_num), 50 | blockId: block.block_id, 51 | stateRootHash: block.state_root_hash 52 | } 53 | } 54 | 55 | // Parse State Delta Event 56 | const getChanges = events => { 57 | const event = events.find(e => e.eventType === 'sawtooth/state-delta') 58 | if (!event) return [] 59 | 60 | const changeList = StateChangeList.decode(event.data) 61 | return changeList.stateChanges 62 | .filter(change => change.address.slice(0, 6) === PREFIX) 63 | } 64 | 65 | // Handle event message received by stream 66 | const handleEvent = msg => { 67 | if (msg.messageType === Message.MessageType.CLIENT_EVENTS) { 68 | const events = EventList.decode(msg.content).events 69 | deltas.handle(getBlock(events), getChanges(events)) 70 | } else { 71 | console.warn('Received message of unknown type:', msg.messageType) 72 | } 73 | } 74 | 75 | // Send delta event subscription request to validator 76 | const subscribe = () => { 77 | const blockSub = EventSubscription.create({ 78 | eventType: 'sawtooth/block-commit' 79 | }) 80 | const deltaSub = EventSubscription.create({ 81 | eventType: 'sawtooth/state-delta', 82 | filters: [EventFilter.create({ 83 | key: 'address', 84 | matchString: `^${PREFIX}.*`, 85 | filterType: EventFilter.FilterType.REGEX_ANY 86 | })] 87 | }) 88 | 89 | return stream.send( 90 | Message.MessageType.CLIENT_EVENTS_SUBSCRIBE_REQUEST, 91 | ClientEventsSubscribeRequest.encode({ 92 | lastKnownBlockIds: [NULL_BLOCK_ID], 93 | subscriptions: [blockSub, deltaSub] 94 | }).finish() 95 | ) 96 | .then(response => ClientEventsSubscribeResponse.decode(response)) 97 | .then(decoded => { 98 | const status = _.findKey(ClientEventsSubscribeResponse.Status, 99 | val => val === decoded.status) 100 | if (status !== 'OK') { 101 | throw new Error(`Validator responded with status "${status}"`) 102 | } 103 | }) 104 | } 105 | 106 | // Start stream and send delta event subscription request 107 | const start = () => { 108 | return new Promise(resolve => { 109 | stream.connect(() => { 110 | stream.onReceive(handleEvent) 111 | subscribe().then(resolve) 112 | }) 113 | }) 114 | } 115 | 116 | module.exports = { 117 | start 118 | } 119 | -------------------------------------------------------------------------------- /ledger_sync/subscriber/protos.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const path = require('path') 20 | const _ = require('lodash') 21 | const protobuf = require('protobufjs') 22 | 23 | const protos = {} 24 | 25 | const loadProtos = (filename, protoNames) => { 26 | const protoPath = path.resolve(__dirname, '../../protos', filename) 27 | return protobuf.load(protoPath) 28 | .then(root => { 29 | protoNames.forEach(name => { 30 | protos[name] = root.lookupType(name) 31 | }) 32 | }) 33 | } 34 | 35 | const compile = () => { 36 | return Promise.all([ 37 | loadProtos('agent.proto', [ 38 | 'Agent', 39 | 'AgentContainer' 40 | ]), 41 | loadProtos('property.proto', [ 42 | 'Property', 43 | 'PropertyContainer', 44 | 'PropertyPage', 45 | 'PropertyPageContainer', 46 | 'PropertySchema', 47 | 'PropertyValue', 48 | 'Location' 49 | ]), 50 | loadProtos('proposal.proto', [ 51 | 'Proposal', 52 | 'ProposalContainer' 53 | ]), 54 | loadProtos('record.proto', [ 55 | 'Record', 56 | 'RecordContainer', 57 | 'RecordType', 58 | 'RecordTypeContainer' 59 | ]), 60 | loadProtos('payload.proto', [ 61 | 'SCPayload', 62 | 'CreateAgentAction', 63 | 'FinalizeRecordAction', 64 | 'CreateRecordAction', 65 | 'CreateRecordTypeAction', 66 | 'UpdatePropertiesAction', 67 | 'CreateProposalAction', 68 | 'AnswerProposalAction', 69 | 'RevokeReporterAction' 70 | ]) 71 | ]) 72 | } 73 | 74 | module.exports = _.assign(protos, { compile }) 75 | -------------------------------------------------------------------------------- /ledger_sync/system/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const loadConfig = (defaultValue = {}) => { 20 | try { 21 | return require('../config.json') 22 | } catch (err) { 23 | // Throw error on bad JSON, otherwise ignore 24 | if (err instanceof SyntaxError) throw err 25 | return {} 26 | } 27 | } 28 | 29 | const config = loadConfig() 30 | 31 | const initConfigValue = (key, defaultValue = null) => { 32 | config[key] = process.env[key] || config[key] || defaultValue 33 | } 34 | 35 | // Setup non-sensitive config variable with sensible defaults, 36 | // if not set in environment variables or config.json 37 | initConfigValue('RETRY_WAIT', 5000) 38 | initConfigValue('VALIDATOR_URL', 'tcp://localhost:4004') 39 | initConfigValue('DB_HOST', 'localhost') 40 | initConfigValue('DB_PORT', 28015) 41 | initConfigValue('DB_NAME', 'supply_chain') 42 | 43 | module.exports = config 44 | -------------------------------------------------------------------------------- /nose2.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | start-dir = tests 3 | code-directories = .. 4 | test-file-pattern = test*.py 5 | plugins = nose2.plugins.coverage 6 | 7 | [coverage] 8 | always-on = True 9 | -------------------------------------------------------------------------------- /processor/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Cargill Incorporated 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [package] 16 | name = "supply-chain-tp" 17 | version = "0.10.1" 18 | authors = ["Cargill Incorporated"] 19 | description = "Sawtooth Supply Chain Transaction Processor" 20 | homepage = "https://github.com/hyperledger/sawtooth-supply-chain" 21 | 22 | [dependencies] 23 | sawtooth-sdk = "^0.2" 24 | rust-crypto = "0.2.36" 25 | rustc-serialize = "0.3.22" 26 | sawtooth-zmq = "0.8.2-dev5" 27 | clap = "2" 28 | protobuf = "2" 29 | log = "0.3.0" 30 | log4rs = "0.7.0" 31 | 32 | [build-dependencies] 33 | protoc-rust = "2" 34 | glob = "0.2" 35 | -------------------------------------------------------------------------------- /processor/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Cargill Incorporated 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM rust:1 16 | 17 | RUN apt-get update && apt-get install -y unzip libzmq3-dev 18 | 19 | RUN \ 20 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 21 | http_proxy=$HTTP_PROXY; \ 22 | fi; \ 23 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 24 | https_proxy=$HTTPS_PROXY; \ 25 | fi; \ 26 | if [ ! -z $http_proxy ]; then \ 27 | http_proxy_host=$(printf $http_proxy | sed 's|http.*://\(.*\):\(.*\)$|\1|');\ 28 | http_proxy_port=$(printf $http_proxy | sed 's|http.*://\(.*\):\(.*\)$|\2|');\ 29 | mkdir -p $HOME/.cargo \ 30 | && echo "[http]" >> $HOME/.cargo/config \ 31 | && echo 'proxy = "'$http_proxy_host:$http_proxy_port'"' >> $HOME/.cargo/config \ 32 | && cat $HOME/.cargo/config; \ 33 | fi; \ 34 | if [ ! -z $https_proxy ]; then \ 35 | https_proxy_host=$(printf $https_proxy | sed 's|http.*://\(.*\):\(.*\)$|\1|');\ 36 | https_proxy_port=$(printf $https_proxy | sed 's|http.*://\(.*\):\(.*\)$|\2|');\ 37 | mkdir -p $HOME/.cargo \ 38 | && echo "[https]" >> $HOME/.cargo/config \ 39 | && echo 'proxy = "'$https_proxy_host:$https_proxy_port'"' >> $HOME/.cargo/config \ 40 | && cat $HOME/.cargo/config; \ 41 | fi; 42 | 43 | # For Building Protobufs 44 | RUN curl -OLsS https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip \ 45 | && unzip protoc-3.5.1-linux-x86_64.zip -d protoc3 \ 46 | && rm protoc-3.5.1-linux-x86_64.zip 47 | RUN apt-get update && apt-get install -y protobuf-compiler 48 | 49 | # Build TP with dummy source in order to cache dependencies in Docker image. 50 | # Make sure not to use the `volumes` command to overwrite: 51 | # - /sawtooth-supply-chain/processor/target/ 52 | # - /sawtooth-supply-chain/processor/src/messages/ 53 | WORKDIR /sawtooth-supply-chain 54 | RUN USER=root cargo new --bin processor 55 | 56 | WORKDIR /sawtooth-supply-chain/processor 57 | COPY Cargo.toml Cargo.lock* ./ 58 | RUN cargo build 59 | 60 | ENV PATH=$PATH:/sawtooth-supply-chain/processor/target/debug/ 61 | 62 | ENTRYPOINT ["/sawtooth-supply-chain/processor/target/debug/supply-chain-tp"] 63 | -------------------------------------------------------------------------------- /processor/Dockerfile-installed-bionic: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Cargill Incorporated 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # docker build -f processor/Dockerfile-installed -t supply-chain-tp . 16 | 17 | # -------------=== supply-chain tp build ===------------- 18 | FROM ubuntu:bionic as supply-chain-tp-builder 19 | 20 | ENV VERSION=AUTO_STRICT 21 | 22 | RUN apt-get update \ 23 | && apt-get install -y \ 24 | curl \ 25 | gcc \ 26 | libssl-dev \ 27 | libzmq3-dev \ 28 | pkg-config \ 29 | unzip 30 | 31 | # For Building Protobufs 32 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ 33 | && curl -OLsS https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip \ 34 | && unzip protoc-3.5.1-linux-x86_64.zip -d protoc3 \ 35 | && rm protoc-3.5.1-linux-x86_64.zip 36 | 37 | ENV PATH=$PATH:/protoc3/bin 38 | RUN /root/.cargo/bin/cargo install cargo-deb 39 | 40 | COPY . /project 41 | 42 | WORKDIR /project/processor 43 | 44 | RUN /root/.cargo/bin/cargo deb 45 | 46 | # -------------=== supply-chain-tp docker build ===------------- 47 | FROM ubuntu:bionic 48 | 49 | COPY --from=supply-chain-tp-builder /project/processor/target/debian/supply-chain-tp*.deb /tmp 50 | 51 | RUN apt-get update \ 52 | && dpkg -i /tmp/supply-chain-tp*.deb || true \ 53 | && apt-get -f -y install 54 | 55 | CMD ["supply-chain-tp", "-vv"] 56 | -------------------------------------------------------------------------------- /processor/Dockerfile-installed-xenial: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Cargill Incorporated 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # docker build -f processor/Dockerfile-installed -t supply-chain-tp . 16 | 17 | # -------------=== supply-chain tp build ===------------- 18 | FROM ubuntu:xenial as supply-chain-tp-builder 19 | 20 | ENV VERSION=AUTO_STRICT 21 | 22 | RUN apt-get update \ 23 | && apt-get install -y \ 24 | curl \ 25 | gcc \ 26 | libssl-dev \ 27 | libzmq3-dev \ 28 | pkg-config \ 29 | unzip 30 | 31 | # For Building Protobufs 32 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ 33 | && curl -OLsS https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip \ 34 | && unzip protoc-3.5.1-linux-x86_64.zip -d protoc3 \ 35 | && rm protoc-3.5.1-linux-x86_64.zip 36 | 37 | ENV PATH=$PATH:/protoc3/bin 38 | RUN /root/.cargo/bin/cargo install cargo-deb 39 | 40 | COPY . /project 41 | 42 | WORKDIR /project/processor 43 | 44 | RUN /root/.cargo/bin/cargo deb 45 | 46 | # -------------=== supply-chain-tp docker build ===------------- 47 | FROM ubuntu:xenial 48 | 49 | COPY --from=supply-chain-tp-builder /project/processor/target/debian/supply-chain-tp*.deb /tmp 50 | 51 | RUN apt-get update \ 52 | && dpkg -i /tmp/supply-chain-tp*.deb || true \ 53 | && apt-get -f -y install 54 | 55 | CMD ["supply-chain-tp", "-vv"] 56 | -------------------------------------------------------------------------------- /processor/build.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ------------------------------------------------------------------------------ 16 | */ 17 | 18 | extern crate glob; 19 | extern crate protoc_rust; 20 | 21 | use std::fs; 22 | use std::io::Write; 23 | use protoc_rust::Customize; 24 | 25 | fn main() { 26 | // Generate protobuf files 27 | let proto_src_files = glob_simple("../protos/*.proto"); 28 | println!("{:?}", proto_src_files); 29 | 30 | fs::create_dir_all("src/messages").unwrap(); 31 | 32 | protoc_rust::run(protoc_rust::Args { 33 | out_dir: "src/messages", 34 | input: &proto_src_files 35 | .iter() 36 | .map(|a| a.as_ref()) 37 | .collect::>(), 38 | includes: &["src", "../protos"], 39 | customize: Customize::default(), 40 | }).expect("unable to run protoc"); 41 | 42 | let mut file = fs::File::create("src/messages/mod.rs").unwrap(); 43 | for filename in proto_src_files.iter() { 44 | file.write_all(path_to_mod(&filename).as_bytes()).unwrap(); 45 | } 46 | } 47 | 48 | fn path_to_mod(filename: &String) -> String { 49 | filename.replace("../protos/", "pub mod ").replace(".proto", ";\n") 50 | } 51 | 52 | fn glob_simple(pattern: &str) -> Vec { 53 | glob::glob(pattern) 54 | .expect("glob") 55 | .map(|g| { 56 | g.expect("item") 57 | .as_path() 58 | .to_str() 59 | .expect("utf-8") 60 | .to_owned() 61 | }) 62 | .collect() 63 | } 64 | -------------------------------------------------------------------------------- /processor/src/addressing.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Cargill Incorporated 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crypto::digest::Digest; 16 | use crypto::sha2::Sha512; 17 | 18 | const FAMILY_NAME: &str = "supply_chain"; 19 | const AGENT: &str = "ae"; 20 | const PROPERTY: &str = "ea"; 21 | const PROPOSAL: &str = "aa"; 22 | const RECORD: &str = "ec"; 23 | const RECORD_TYPE: &str = "ee"; 24 | 25 | pub fn get_supply_chain_prefix() -> String { 26 | let mut sha = Sha512::new(); 27 | sha.input_str(&FAMILY_NAME); 28 | sha.result_str()[..6].to_string() 29 | } 30 | 31 | pub fn hash(to_hash: &str, num: usize) -> String { 32 | let mut sha = Sha512::new(); 33 | sha.input_str(to_hash); 34 | let temp = sha.result_str().to_string(); 35 | let hash = match temp.get(..num) { 36 | Some(x) => x, 37 | None => "", 38 | }; 39 | hash.to_string() 40 | } 41 | 42 | pub fn make_agent_address(identifier: &str) -> String { 43 | get_supply_chain_prefix() + &AGENT + &hash(identifier, 62) 44 | } 45 | 46 | pub fn make_record_address(record_id: &str) -> String { 47 | get_supply_chain_prefix() + &RECORD + &hash(record_id, 62) 48 | } 49 | 50 | pub fn make_record_type_address(type_name: &str) -> String { 51 | get_supply_chain_prefix() + &RECORD_TYPE + &hash(type_name, 62) 52 | } 53 | 54 | pub fn make_property_address(record_id: &str, property_name: &str, page: u32) -> String { 55 | make_property_address_range(record_id) + &hash(property_name, 22) + &num_to_page_number(page) 56 | } 57 | 58 | pub fn make_property_address_range(record_id: &str) -> String { 59 | get_supply_chain_prefix() + &PROPERTY + &hash(record_id, 36) 60 | } 61 | 62 | pub fn num_to_page_number(page: u32) -> String { 63 | format!("{:01$x}", page, 4) 64 | } 65 | 66 | pub fn make_proposal_address(record_id: &str, agent_id: &str) -> String { 67 | get_supply_chain_prefix() + PROPOSAL + &hash(record_id, 36) + &hash(agent_id, 26) 68 | } 69 | -------------------------------------------------------------------------------- /processor/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Cargill Incorporated 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[macro_use] 16 | extern crate clap; 17 | extern crate crypto; 18 | extern crate log4rs; 19 | #[macro_use] 20 | extern crate log; 21 | extern crate protobuf; 22 | extern crate rustc_serialize; 23 | extern crate sawtooth_sdk; 24 | 25 | mod handler; 26 | mod addressing; 27 | mod messages; 28 | 29 | use std::process; 30 | use log::LogLevelFilter; 31 | use log4rs::append::console::ConsoleAppender; 32 | use log4rs::config::{Appender, Config, Root}; 33 | use log4rs::encode::pattern::PatternEncoder; 34 | 35 | use sawtooth_sdk::processor::TransactionProcessor; 36 | 37 | use handler::SupplyChainTransactionHandler; 38 | 39 | fn main() { 40 | let matches = clap_app!(intkey => 41 | (version: crate_version!()) 42 | (about: "SupplyChain Transaction Processor (Rust)") 43 | (@arg connect: -C --connect +takes_value 44 | "connection endpoint for validator") 45 | (@arg verbose: -v --verbose +multiple 46 | "increase output verbosity")) 47 | .get_matches(); 48 | 49 | let endpoint = matches 50 | .value_of("connect") 51 | .unwrap_or("tcp://localhost:4004"); 52 | 53 | let console_log_level; 54 | match matches.occurrences_of("verbose") { 55 | 0 => console_log_level = LogLevelFilter::Warn, 56 | 1 => console_log_level = LogLevelFilter::Info, 57 | 2 => console_log_level = LogLevelFilter::Debug, 58 | 3 | _ => console_log_level = LogLevelFilter::Trace, 59 | } 60 | 61 | let stdout = ConsoleAppender::builder() 62 | .encoder(Box::new(PatternEncoder::new( 63 | "{h({l:5.5})} | {({M}:{L}):20.20} | {m}{n}", 64 | ))) 65 | .build(); 66 | 67 | let config = match Config::builder() 68 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 69 | .build(Root::builder().appender("stdout").build(console_log_level)) 70 | { 71 | Ok(x) => x, 72 | Err(_) => process::exit(1), 73 | }; 74 | 75 | match log4rs::init_config(config) { 76 | Ok(_) => (), 77 | Err(_) => process::exit(1), 78 | } 79 | 80 | let handler = SupplyChainTransactionHandler::new(); 81 | let mut processor = TransactionProcessor::new(endpoint); 82 | 83 | info!("Console logging level: {}", console_log_level); 84 | 85 | processor.add_handler(&handler); 86 | processor.start(); 87 | } 88 | -------------------------------------------------------------------------------- /protos/agent.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Intel Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | syntax = "proto3"; 17 | 18 | 19 | message Agent { 20 | string public_key = 1; 21 | 22 | // A human readable name identifying the Agent 23 | string name = 2; 24 | 25 | // Unix UTC timestamp of approximately when this agent was registered 26 | uint64 timestamp = 3; 27 | } 28 | 29 | 30 | message AgentContainer { 31 | repeated Agent entries = 1; 32 | } 33 | -------------------------------------------------------------------------------- /protos/payload.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Intel Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | syntax = "proto3"; 17 | 18 | import "property.proto"; 19 | import "proposal.proto"; 20 | 21 | 22 | message SCPayload { 23 | enum Action { 24 | CREATE_AGENT = 0; 25 | CREATE_RECORD = 1; 26 | FINALIZE_RECORD = 2; 27 | CREATE_RECORD_TYPE = 3; 28 | UPDATE_PROPERTIES = 4; 29 | CREATE_PROPOSAL = 5; 30 | ANSWER_PROPOSAL = 6; 31 | REVOKE_REPORTER = 7; 32 | } 33 | 34 | Action action = 1; 35 | 36 | // Approximately when transaction was submitted, as a Unix UTC 37 | // timestamp 38 | uint64 timestamp = 2; 39 | 40 | // The transaction handler will read from just one of these fields 41 | // according to the Action. 42 | CreateAgentAction create_agent = 3; 43 | CreateRecordAction create_record = 4; 44 | FinalizeRecordAction finalize_record = 5; 45 | CreateRecordTypeAction create_record_type = 6; 46 | UpdatePropertiesAction update_properties = 7; 47 | CreateProposalAction create_proposal = 8; 48 | AnswerProposalAction answer_proposal = 9; 49 | RevokeReporterAction revoke_reporter = 10; 50 | } 51 | 52 | 53 | message CreateAgentAction { 54 | // The human-readable name of the Agent. This does not need to be 55 | // unique. 56 | string name = 1; 57 | } 58 | 59 | 60 | message CreateRecordAction { 61 | // The natural key of the Record 62 | string record_id = 1; 63 | 64 | // The name of the RecordType this Record belongs to 65 | string record_type = 2; 66 | 67 | repeated PropertyValue properties = 3; 68 | } 69 | 70 | 71 | message FinalizeRecordAction { 72 | // The natural key of the Record 73 | string record_id = 1; 74 | } 75 | 76 | 77 | message CreateRecordTypeAction { 78 | string name = 1; 79 | 80 | repeated PropertySchema properties = 2; 81 | } 82 | 83 | 84 | message UpdatePropertiesAction { 85 | // The natural key of the Record 86 | string record_id = 1; 87 | 88 | repeated PropertyValue properties = 2; 89 | } 90 | 91 | 92 | message CreateProposalAction { 93 | // The natural key of the Record 94 | string record_id = 1; 95 | 96 | // the public key of the Agent to whom the Proposal is sent 97 | // (must be different from the Agent creating the Proposal) 98 | string receiving_agent = 2; 99 | 100 | Proposal.Role role = 3; 101 | 102 | repeated string properties = 4; 103 | } 104 | 105 | 106 | message AnswerProposalAction { 107 | enum Response { 108 | ACCEPT = 0; 109 | REJECT = 1; 110 | CANCEL = 2; 111 | } 112 | 113 | // The natural key of the Record 114 | string record_id = 1; 115 | 116 | // The public key of the Agent to whom the proposal is sent 117 | string receiving_agent = 2; 118 | 119 | // The role being proposed (owner, custodian, or reporter) 120 | Proposal.Role role = 3; 121 | 122 | // The respose to the Proposal (accept, reject, or cancel) 123 | Response response = 4; 124 | } 125 | 126 | 127 | message RevokeReporterAction { 128 | // The natural key of the Record 129 | string record_id = 1; 130 | 131 | // The reporter's public key 132 | string reporter_id = 2; 133 | 134 | // The names of the Properties for which the reporter's 135 | // authorization is revoked 136 | repeated string properties = 3; 137 | } 138 | -------------------------------------------------------------------------------- /protos/proposal.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Intel Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | syntax = "proto3"; 17 | 18 | 19 | message Proposal { 20 | enum Role { 21 | OWNER = 0; 22 | CUSTODIAN = 1; 23 | REPORTER = 2; 24 | } 25 | 26 | enum Status { 27 | OPEN = 0; 28 | ACCEPTED = 1; 29 | REJECTED = 2; 30 | CANCELED = 3; 31 | } 32 | 33 | string record_id = 1; 34 | 35 | // The time at which the Proposal was created 36 | uint64 timestamp = 2; 37 | 38 | // The public key of the Agent sending the Proposal. This Agent must 39 | // be the owner of the Record (or the custodian, if the Proposal is 40 | // to transfer custodianship). 41 | string issuing_agent = 3; 42 | 43 | // The public key of the Agent to whom the Proposal is sent. 44 | string receiving_agent = 4; 45 | 46 | // What the Proposal is for -- transferring ownership, transferring 47 | // custodianship, or authorizing a reporter. 48 | Role role = 5; 49 | 50 | // The names of properties for which the reporter is being authorized 51 | // (empty for owner or custodian transfers) 52 | repeated string properties = 6; 53 | 54 | // The status of the Proposal. For a given Record and receiving 55 | // Agent, there can be only one open Proposal at a time for each 56 | // role. 57 | Status status = 7; 58 | 59 | // The human-readable terms of transfer. 60 | string terms = 8; 61 | } 62 | 63 | 64 | message ProposalContainer { 65 | repeated Proposal entries = 1; 66 | } 67 | -------------------------------------------------------------------------------- /protos/record.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Intel Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | syntax = "proto3"; 17 | 18 | import "property.proto"; 19 | 20 | 21 | message Record { 22 | message AssociatedAgent { 23 | string agent_id = 1; 24 | uint64 timestamp = 2; 25 | } 26 | 27 | // The user-defined natural key which identifies the object in the 28 | // real world (for example a serial number) 29 | string record_id = 1; 30 | 31 | string record_type = 2; 32 | 33 | // Ordered oldest to newest by timestamp 34 | repeated AssociatedAgent owners = 3; 35 | repeated AssociatedAgent custodians = 4; 36 | 37 | // Flag indicating whether the Record can be updated. If it is set 38 | // to true, then the record has been finalized and no further 39 | // changes can be made to it or its Properties. 40 | bool final = 5; 41 | } 42 | 43 | 44 | message RecordContainer { 45 | repeated Record entries = 1; 46 | } 47 | 48 | 49 | message RecordType { 50 | // A unique human-readable designation for the RecordType 51 | string name = 1; 52 | 53 | repeated PropertySchema properties = 2; 54 | } 55 | 56 | 57 | message RecordTypeContainer { 58 | repeated RecordType entries = 1; 59 | } 60 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:bionic 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | # Install Node and Ubuntu dependencies 25 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 26 | curl \ 27 | ca-certificates \ 28 | gnupg \ 29 | pkg-config \ 30 | build-essential \ 31 | libzmq3-dev \ 32 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 33 | && chmod 755 /tmp/setup-node.sh \ 34 | && /tmp/setup-node.sh \ 35 | && apt-get install nodejs -y -q \ 36 | && rm /tmp/setup-node.sh \ 37 | && apt-get clean \ 38 | && rm -rf /var/lib/apt/lists/* \ 39 | && npm install -g prebuild-install 40 | 41 | WORKDIR /sawtooth-supply-chain/server 42 | 43 | RUN \ 44 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 45 | http_proxy=$HTTP_PROXY; \ 46 | fi; \ 47 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 48 | https_proxy=$HTTPS_PROXY; \ 49 | fi; \ 50 | if [ ! -z $http_proxy ]; then \ 51 | npm config set proxy $http_proxy; \ 52 | fi; \ 53 | if [ ! -z $https_proxy ]; then \ 54 | npm config set https-proxy $https_proxy; \ 55 | fi 56 | 57 | COPY server/package.json . 58 | RUN npm install 59 | 60 | EXPOSE 3000/tcp 61 | 62 | CMD ["/usr/bin/node" "index.js"] 63 | -------------------------------------------------------------------------------- /server/Dockerfile-installed-bionic: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:bionic 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | RUN apt-get update \ 25 | && apt-get install gnupg -y 26 | 27 | # Install Node and Ubuntu dependencies 28 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 29 | curl \ 30 | ca-certificates \ 31 | pkg-config \ 32 | build-essential \ 33 | libzmq3-dev \ 34 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 35 | && chmod 755 /tmp/setup-node.sh \ 36 | && /tmp/setup-node.sh \ 37 | && apt-get install nodejs -y -q \ 38 | && rm /tmp/setup-node.sh \ 39 | && apt-get clean \ 40 | && rm -rf /var/lib/apt/lists/* \ 41 | && npm install -g prebuild-install 42 | 43 | WORKDIR /sawtooth-supply-chain/server 44 | 45 | COPY server/package.json . 46 | RUN npm install 47 | 48 | #Copy client sample data for script use 49 | COPY asset_client/sample_data/ ../asset_client/sample_data/ 50 | COPY fish_client/sample_data/ ../fish_client/sample_data/ 51 | 52 | COPY protos/ ../protos/ 53 | COPY server/ . 54 | 55 | EXPOSE 3000/tcp 56 | 57 | CMD ["/usr/bin/node", "index.js"] 58 | -------------------------------------------------------------------------------- /server/Dockerfile-installed-xenial: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | # Description: 17 | # Builds server and client node dependencies, creating a server image 18 | # which can be run with root context 19 | 20 | FROM ubuntu:xenial 21 | 22 | LABEL "install-type"="mounted" 23 | 24 | # Install Node and Ubuntu dependencies 25 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 26 | curl \ 27 | ca-certificates \ 28 | pkg-config \ 29 | build-essential \ 30 | libzmq3-dev \ 31 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 32 | && chmod 755 /tmp/setup-node.sh \ 33 | && /tmp/setup-node.sh \ 34 | && apt-get install nodejs -y -q \ 35 | && rm /tmp/setup-node.sh \ 36 | && apt-get clean \ 37 | && rm -rf /var/lib/apt/lists/* \ 38 | && npm install -g prebuild-install 39 | 40 | WORKDIR /sawtooth-supply-chain/server 41 | 42 | COPY server/package.json . 43 | RUN npm install 44 | 45 | #Copy client sample data for script use 46 | COPY asset_client/sample_data/ ../asset_client/sample_data/ 47 | COPY fish_client/sample_data/ ../fish_client/sample_data/ 48 | 49 | COPY protos/ ../protos/ 50 | COPY server/ . 51 | 52 | EXPOSE 3000/tcp 53 | 54 | CMD ["/usr/bin/node", "index.js"] 55 | -------------------------------------------------------------------------------- /server/api/agents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const db = require('../db/agents') 21 | 22 | const FILTER_KEYS = ['name', 'publicKey'] 23 | 24 | const list = params => db.list(_.pick(params, FILTER_KEYS)) 25 | 26 | const fetch = ({ publicKey, authedKey }) => db.fetch(publicKey, publicKey === authedKey) 27 | 28 | module.exports = { 29 | list, 30 | fetch 31 | } 32 | -------------------------------------------------------------------------------- /server/api/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const bcrypt = require('bcrypt') 20 | const jwt = require('jsonwebtoken') 21 | 22 | const users = require('../db/users') 23 | const { BadRequest, Unauthorized } = require('./errors') 24 | const config = require('../system/config') 25 | 26 | const SALT_ROUNDS = 10 27 | const SECRET = config.JWT_SECRET 28 | 29 | // Hashes a password as promised 30 | const hashPassword = pass => bcrypt.hash(pass, SALT_ROUNDS) 31 | 32 | // Creates a new JWT token as promised 33 | const createToken = payload => { 34 | return new Promise((resolve, reject) => { 35 | jwt.sign(payload, SECRET, (err, token) => { 36 | if (err) reject(err) 37 | else resolve(token) 38 | }) 39 | }) 40 | } 41 | 42 | // Verifies a token is valid as promised. 43 | // Sends back the decoded payload, or throws an error if invalid. 44 | const verifyToken = token => { 45 | return new Promise((resolve, reject) => { 46 | jwt.verify(token, SECRET, (err, payload) => { 47 | if (err) reject(err) 48 | else resolve(payload) 49 | }) 50 | }) 51 | } 52 | 53 | // Checks an object with username and password keys. 54 | // Returns an auth token and the user's private key if it passes. 55 | const authorize = ({ username, password }) => { 56 | if (!username || !password) { 57 | const message = 'Authorization requires username and password' 58 | return Promise.reject(new BadRequest(message)) 59 | } 60 | 61 | return users.query(users => users.filter({ username })) 62 | .then(matches => { 63 | if (matches.length === 0) throw new Error() 64 | const user = matches[0] 65 | 66 | return bcrypt.compare(password, user.password) 67 | .then(passValid => { 68 | if (!passValid) throw new Error() 69 | return createToken(user.publicKey) 70 | }) 71 | .then(token => ({ 72 | authorization: token, 73 | encryptedKey: user.encryptedKey 74 | })) 75 | }) 76 | .catch(() => { throw new Unauthorized('Authorization Failed') }) 77 | } 78 | 79 | module.exports = { 80 | hashPassword, 81 | createToken, 82 | verifyToken, 83 | authorize 84 | } 85 | -------------------------------------------------------------------------------- /server/api/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | class BadRequest extends Error { 20 | constructor (message) { 21 | super(message) 22 | this.status = 400 23 | } 24 | } 25 | 26 | class Unauthorized extends Error { 27 | constructor (message) { 28 | super(message) 29 | this.status = 401 30 | } 31 | } 32 | 33 | class NotFound extends Error { 34 | constructor (message) { 35 | super(message) 36 | this.status = 404 37 | } 38 | } 39 | 40 | class InternalServerError extends Error { 41 | constructor (message) { 42 | super(message) 43 | this.status = 500 44 | } 45 | } 46 | 47 | module.exports = { 48 | BadRequest, 49 | Unauthorized, 50 | NotFound, 51 | InternalServerError 52 | } 53 | -------------------------------------------------------------------------------- /server/api/record_types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Cargill Incorporated 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const { NotFound } = require('./errors') 21 | const db = require('../db/record_types') 22 | 23 | const FILTER_KEYS = ['name'] 24 | 25 | const fetch = ({ typeName }) => { 26 | return db.fetch(typeName) 27 | .then(resourceType => { 28 | if (!resourceType) { 29 | throw new NotFound(`No resource type with name: ${typeName}`) 30 | } 31 | return resourceType 32 | }) 33 | } 34 | 35 | const list = params => db.list(_.pick(params, FILTER_KEYS)) 36 | 37 | module.exports = { 38 | fetch, 39 | list 40 | } 41 | -------------------------------------------------------------------------------- /server/api/records.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const db = require('../db/records') 21 | 22 | const FILTER_KEYS = ['recordId', 'recordType'] 23 | 24 | const fetchProperty = ({recordId, propertyName}) => { 25 | return db.fetchProperty(recordId, propertyName) 26 | } 27 | 28 | const fetchRecord = ({recordId, authedKey}) => { 29 | return db.fetchRecord(recordId, authedKey) 30 | } 31 | 32 | const listRecords = params => { 33 | return db.listRecords(params.authedKey, _.pick(params, FILTER_KEYS)) 34 | } 35 | 36 | module.exports = { 37 | fetchProperty, 38 | fetchRecord, 39 | listRecords 40 | } 41 | -------------------------------------------------------------------------------- /server/api/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const db = require('../db/users') 21 | const agents = require('../db/agents') 22 | const auth = require('./auth') 23 | const { BadRequest } = require('./errors') 24 | 25 | const create = user => { 26 | return Promise.resolve() 27 | .then(() => { 28 | return agents.fetch(user.publicKey, null) 29 | .catch(() => { 30 | throw new BadRequest('Public key must match an Agent on blockchain') 31 | }) 32 | }) 33 | .then(() => auth.hashPassword(user.password)) 34 | .then(hashed => { 35 | return db.insert(_.assign({}, user, {password: hashed})) 36 | .catch(err => { throw new BadRequest(err.message) }) 37 | }) 38 | .then(() => auth.createToken(user.publicKey)) 39 | .then(token => ({ 40 | authorization: token, 41 | encryptedKey: user.encryptedKey || null 42 | })) 43 | } 44 | 45 | const update = (changes, { authedKey }) => { 46 | return Promise.resolve() 47 | .then(() => { 48 | if (changes.password) { 49 | return auth.hashPassword(changes.password) 50 | .then(hashed => _.set(changes, 'password', hashed)) 51 | } 52 | return changes 53 | }) 54 | .then(finalChanges => db.update(authedKey, finalChanges)) 55 | .then(updated => _.omit(updated, 'password')) 56 | } 57 | 58 | module.exports = { 59 | create, 60 | update 61 | } 62 | -------------------------------------------------------------------------------- /server/blockchain/batcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const secp256k1 = require('sawtooth-sdk/signing/secp256k1') 20 | const { 21 | Batch, 22 | BatchHeader, 23 | TransactionHeader, 24 | TransactionList 25 | } = require('sawtooth-sdk/protobuf') 26 | const { BadRequest } = require('../api/errors') 27 | const config = require('../system/config') 28 | 29 | const PRIVATE_KEY = config.PRIVATE_KEY 30 | 31 | // Initialize secp256k1 Context and PrivateKey wrappers 32 | const context = new secp256k1.Secp256k1Context() 33 | const privateKey = secp256k1.Secp256k1PrivateKey.fromHex(PRIVATE_KEY) 34 | const publicKeyHex = context.getPublicKey(privateKey).asHex() 35 | console.log(`Batch signer initialized with public key: ${publicKeyHex}`) 36 | 37 | // Decode transaction headers and throw errors if invalid 38 | const validateTxns = txns => { 39 | const headers = txns.map(txn => TransactionHeader.decode(txn.header)) 40 | 41 | headers.forEach(header => { 42 | if (header.batcherPublicKey !== publicKeyHex) { 43 | throw new BadRequest( 44 | `Transactions must use batcherPublicKey: ${publicKeyHex}`) 45 | } 46 | }) 47 | } 48 | 49 | // Wrap an array of transactions in an encoded BatchList 50 | const batchTxns = txns => { 51 | const header = BatchHeader.encode({ 52 | signerPublicKey: publicKeyHex, 53 | transactionIds: txns.map(txn => txn.headerSignature) 54 | }).finish() 55 | 56 | return Batch.create({ 57 | header, 58 | headerSignature: context.sign(header, privateKey), 59 | transactions: txns 60 | }) 61 | } 62 | 63 | // Validate an encoded TransactionList, then wrap in an encoded BatchList 64 | const batch = txnList => { 65 | const txns = TransactionList.decode(txnList).transactions 66 | validateTxns(txns) 67 | return batchTxns(txns) 68 | } 69 | 70 | // Return the server's hex encoded public key 71 | const getPublicKey = () => publicKeyHex 72 | 73 | module.exports = { 74 | batch, 75 | getPublicKey 76 | } 77 | -------------------------------------------------------------------------------- /server/blockchain/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const { Stream } = require('sawtooth-sdk/messaging/stream') 21 | const { 22 | Message, 23 | ClientBatchSubmitRequest, 24 | ClientBatchSubmitResponse, 25 | ClientBatchStatus, 26 | ClientBatchStatusRequest, 27 | ClientBatchStatusResponse 28 | } = require('sawtooth-sdk/protobuf') 29 | 30 | const batcher = require('./batcher') 31 | const config = require('../system/config') 32 | 33 | const VALIDATOR_URL = config.VALIDATOR_URL 34 | const stream = new Stream(VALIDATOR_URL) 35 | 36 | const connect = () => { 37 | return new Promise(resolve => stream.connect(resolve)) 38 | .then(() => { 39 | stream.onReceive(msg => { 40 | console.warn('Received message of unknown type:', msg.messageType) 41 | }) 42 | }) 43 | } 44 | 45 | const submit = (txnBytes, { wait }) => { 46 | const batch = batcher.batch(txnBytes) 47 | 48 | return stream.send( 49 | Message.MessageType.CLIENT_BATCH_SUBMIT_REQUEST, 50 | ClientBatchSubmitRequest.encode({ 51 | batches: [batch] 52 | }).finish() 53 | ) 54 | .then(response => ClientBatchSubmitResponse.decode(response)) 55 | .then((decoded) => { 56 | const submitStatus = _.findKey(ClientBatchSubmitResponse.Status, 57 | val => val === decoded.status) 58 | if (submitStatus !== 'OK') { 59 | throw new Error(`Batch submission failed with status '${submitStatus}'`) 60 | } 61 | 62 | if (wait === null) { 63 | return { batch: batch.headerSignature } 64 | } 65 | 66 | return stream.send( 67 | Message.MessageType.CLIENT_BATCH_STATUS_REQUEST, 68 | ClientBatchStatusRequest.encode({ 69 | batchIds: [batch.headerSignature], 70 | wait: true, 71 | timeout: wait 72 | }).finish() 73 | ) 74 | .then(statusResponse => { 75 | const statusBody = ClientBatchStatusResponse 76 | .decode(statusResponse) 77 | .batchStatuses[0] 78 | 79 | if (statusBody.status !== ClientBatchStatus.Status.COMMITTED) { 80 | const id = statusBody.batchId 81 | const status = _.findKey(ClientBatchStatus.Status, 82 | val => val === statusBody.status) 83 | const message = statusBody.invalidTransactions.length > 0 84 | ? statusBody.invalidTransactions[0].message 85 | : '' 86 | throw new Error(`Batch ${id} is ${status}, with message: ${message}`) 87 | } 88 | 89 | // Wait to return until new block is in database 90 | return new Promise(resolve => setTimeout(() => { 91 | resolve({ batch: batch.headerSignature }) 92 | }, 1000)) 93 | }) 94 | }) 95 | } 96 | 97 | module.exports = { 98 | connect, 99 | submit 100 | } 101 | -------------------------------------------------------------------------------- /server/blockchain/protos.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const path = require('path') 20 | const _ = require('lodash') 21 | const protobuf = require('protobufjs') 22 | 23 | const protos = {} 24 | 25 | const loadProtos = (filename, protoNames) => { 26 | const protoPath = path.resolve(__dirname, '../../protos', filename) 27 | return protobuf.load(protoPath) 28 | .then(root => { 29 | protoNames.forEach(name => { 30 | protos[name] = root.lookupType(name) 31 | }) 32 | }) 33 | } 34 | 35 | const compile = () => { 36 | return Promise.all([ 37 | loadProtos('agent.proto', [ 38 | 'Agent', 39 | 'AgentContainer' 40 | ]), 41 | loadProtos('property.proto', [ 42 | 'Property', 43 | 'PropertyContainer', 44 | 'PropertyPage', 45 | 'PropertyPageContainer', 46 | 'PropertySchema', 47 | 'PropertyValue', 48 | 'Location' 49 | ]), 50 | loadProtos('proposal.proto', [ 51 | 'Proposal', 52 | 'ProposalContainer' 53 | ]), 54 | loadProtos('record.proto', [ 55 | 'Record', 56 | 'RecordContainer', 57 | 'RecordType', 58 | 'RecordTypeContainer' 59 | ]), 60 | loadProtos('payload.proto', [ 61 | 'SCPayload', 62 | 'CreateAgentAction', 63 | 'FinalizeRecordAction', 64 | 'CreateRecordAction', 65 | 'CreateRecordTypeAction', 66 | 'UpdatePropertiesAction', 67 | 'CreateProposalAction', 68 | 'AnswerProposalAction', 69 | 'RevokeReporterAction' 70 | ]) 71 | ]) 72 | } 73 | 74 | module.exports = _.assign(protos, { compile }) 75 | -------------------------------------------------------------------------------- /server/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "JWT_SECRET": "shhhhh", 4 | "PRIVATE_KEY": "1111111111111111111111111111111111111111111111111111111111111111", 5 | "MAPS_API_KEY": "https://developers.google.com/maps/documentation/javascript/get-api-key", 6 | 7 | "PORT": 3000, 8 | "RETRY_WAIT": 5000, 9 | "DEFAULT_SUBMIT_WAIT": 500000, 10 | "VALIDATOR_URL": "tcp://localhost:4004", 11 | 12 | "DB_HOST": "localhost", 13 | "DB_PORT": 28015, 14 | "DB_NAME": "supply_chain" 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/db/agents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const r = require('rethinkdb') 20 | 21 | const db = require('./') 22 | 23 | const hasCurrentBlock = currentBlock => obj => { 24 | return r.and( 25 | obj('startBlockNum').le(currentBlock), 26 | obj('endBlockNum').gt(currentBlock) 27 | ) 28 | } 29 | 30 | const getAttribute = attr => obj => obj(attr) 31 | const getRecordId = getAttribute('recordId') 32 | const getPublicKey = getAttribute('publicKey') 33 | const getName = getAttribute('name') 34 | const getReporters = getAttribute('reporters') 35 | const getAuthorized = getAttribute('authorized') 36 | 37 | const hasPublicKey = key => obj => { 38 | return r.eq( 39 | key, 40 | getPublicKey(obj) 41 | ) 42 | } 43 | 44 | const getAssociatedAgentId = role => record => record(role).nth(-1)('agentId') 45 | const getOwnerId = getAssociatedAgentId('owners') 46 | const getCustodianId = getAssociatedAgentId('custodians') 47 | 48 | const isAssociatedWithRecord = association => agent => record => { 49 | return r.eq( 50 | association(record), 51 | getPublicKey(agent) 52 | ) 53 | } 54 | 55 | const isRecordOwner = isAssociatedWithRecord(getOwnerId) 56 | const isRecordCustodian = isAssociatedWithRecord(getCustodianId) 57 | 58 | const isReporter = agent => property => { 59 | return getReporters(property) 60 | .filter(hasPublicKey(getPublicKey(agent))) 61 | .do(seq => r.branch( 62 | seq.isEmpty(), 63 | false, 64 | getAuthorized(seq.nth(0)) 65 | )) 66 | } 67 | 68 | const getTable = (tableName, block) => 69 | r.table(tableName).filter(hasCurrentBlock(block)) 70 | 71 | const listQuery = filterQuery => block => { 72 | return getTable('agents', block) 73 | .filter(filterQuery) 74 | .map(agent => r.expr({ 75 | 'name': getName(agent), 76 | 'key': getPublicKey(agent), 77 | 'owns': getTable('records', block) 78 | .filter(isRecordOwner(agent)) 79 | .map(getRecordId) 80 | .distinct(), 81 | 'custodian': getTable('records', block) 82 | .filter(isRecordCustodian(agent)) 83 | .map(getRecordId) 84 | .distinct(), 85 | 'reports': getTable('properties', block) 86 | .filter(isReporter(agent)) 87 | .map(getRecordId) 88 | .distinct() 89 | })).coerceTo('array') 90 | } 91 | 92 | const fetchQuery = (publicKey, auth) => block => { 93 | return getTable('agents', block) 94 | .filter(hasPublicKey(publicKey)) 95 | .pluck('name', 'publicKey') 96 | .nth(0) 97 | .do( 98 | agent => { 99 | return r.branch( 100 | auth, 101 | agent.merge( 102 | fetchUser(publicKey)), 103 | agent) 104 | }) 105 | } 106 | 107 | const fetchUser = publicKey => { 108 | return r.table('users') 109 | .filter(hasPublicKey(publicKey)) 110 | .pluck('username', 'email', 'encryptedKey') 111 | .nth(0) 112 | } 113 | 114 | const list = filterQuery => db.queryWithCurrentBlock(listQuery(filterQuery)) 115 | 116 | const fetch = (publicKey, auth) => 117 | db.queryWithCurrentBlock(fetchQuery(publicKey, auth)) 118 | 119 | module.exports = { 120 | list, 121 | fetch 122 | } 123 | -------------------------------------------------------------------------------- /server/db/record_types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Cargill Incorporated 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const r = require('rethinkdb') 20 | const db = require('./') 21 | 22 | // Returns true if a resource is included in the block with the passed number 23 | const fromBlock = blockNum => resource => { 24 | return r.and( 25 | resource('startBlockNum').le(blockNum), 26 | resource('endBlockNum').gt(blockNum)) 27 | } 28 | 29 | // Transforms an array of resources with a "name" property 30 | // to an object where names are the keys 31 | const arrayToObject = namedResources => { 32 | return r.object(r.args(namedResources.concatMap(resource => { 33 | return [ resource('name'), resource.without('name') ] 34 | }))) 35 | } 36 | 37 | // Transforms raw recordType entity into the publishable form the API expects 38 | const publishRecordType = type => { 39 | return r.expr({ 40 | name: type('name'), 41 | properties: arrayToObject(type('properties')) 42 | }) 43 | } 44 | 45 | const fetchQuery = name => currentBlock => { 46 | return r.table('recordTypes') 47 | .getAll(name, { index: 'name' }) 48 | .filter(fromBlock(currentBlock)) 49 | .map(publishRecordType) 50 | .nth(0) 51 | .default(null) 52 | } 53 | 54 | const listQuery = filterQuery => currentBlock => { 55 | return r.table('recordTypes') 56 | .filter(fromBlock(currentBlock)) 57 | .filter(filterQuery) 58 | .map(publishRecordType) 59 | .coerceTo('array') 60 | } 61 | 62 | const fetch = name => db.queryWithCurrentBlock(fetchQuery(name)) 63 | 64 | const list = filterQuery => db.queryWithCurrentBlock(listQuery(filterQuery)) 65 | 66 | module.exports = { 67 | fetch, 68 | list 69 | } 70 | -------------------------------------------------------------------------------- /server/db/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const db = require('./') 21 | 22 | const USER_SCHEMA = { 23 | username: String, 24 | password: String, 25 | email: /.+@.+\..+/, 26 | publicKey: String, 27 | '?encryptedKey': String, 28 | '*': null 29 | } 30 | 31 | // Modified user schema with optional keys 32 | const UPDATE_SCHEMA = _.mapKeys(USER_SCHEMA, (_, key) => { 33 | if (key === '*' || key[0] === '?') return key 34 | return '?' + key 35 | }) 36 | 37 | const query = query => db.queryTable('users', query) 38 | 39 | const insert = user => { 40 | return db.validate(user, USER_SCHEMA) 41 | .then(() => db.insertTable('users', user)) 42 | .then(results => { 43 | return db.insertTable('usernames', {username: user.username}) 44 | .then(() => results) 45 | .catch(err => { 46 | // Delete user, before re-throwing error 47 | return db.modifyTable('users', users => { 48 | return users.get(user.publicKey).delete() 49 | }) 50 | .then(() => { throw err }) 51 | }) 52 | }) 53 | } 54 | 55 | const update = (publicKey, changes) => { 56 | return db.validate(changes, UPDATE_SCHEMA) 57 | .then(() => db.updateTable('users', publicKey, changes)) 58 | .then(results => { 59 | // If changes did not change the resource, just fetch it 60 | if (results.unchanged === 1) { 61 | return db.queryTable('users', users => users.get(publicKey), false) 62 | } 63 | 64 | const oldUser = results.changes[0].old_val 65 | const newUser = results.changes[0].new_val 66 | 67 | // If username did not change, send back new users 68 | if (!changes.username) return newUser 69 | 70 | // Modify usernames table with new name 71 | return db.modifyTable('usernames', usernames => { 72 | return usernames.get(oldUser.username).delete().do(() => { 73 | return usernames.insert({username: changes.username}) 74 | }) 75 | }) 76 | .then(() => newUser) 77 | .catch(err => { 78 | // If failed to update usernames, reset user and re-throw error 79 | return db.updateTable('users', publicKey, oldUser) 80 | .then(() => { throw err }) 81 | }) 82 | }) 83 | } 84 | 85 | module.exports = { 86 | query, 87 | insert, 88 | update 89 | } 90 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const express = require('express') 20 | const db = require('./db') 21 | const blockchain = require('./blockchain') 22 | const protos = require('./blockchain/protos') 23 | const api = require('./api') 24 | const config = require('./system/config') 25 | 26 | const PORT = config.PORT 27 | const app = express() 28 | 29 | Promise.all([ 30 | db.connect(), 31 | protos.compile(), 32 | blockchain.connect() 33 | ]) 34 | .then(() => { 35 | app.use('/', api) 36 | app.listen(PORT, () => { 37 | console.log(`Supply Chain Server listening on port ${PORT}`) 38 | }) 39 | }) 40 | .catch(err => console.error(err.message)) 41 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supply_chain_server", 3 | "version": "0.0.0", 4 | "description": "A database and API for clients using Sawtooth Supply Chain", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "watch": "nodemon index.js", 12 | "init": "node ./scripts/bootstrap_database.js", 13 | "test": "standard", 14 | "make-asset": "DATA=\"../../asset_client/sample_data/core_types.json\" node ./scripts/seed_core_types.js", 15 | "seed-sample-assets": "DATA=\"../../asset_client/sample_data/sample_data.json\" node ./scripts/seed_sample_data.js", 16 | "update-sample-assets": "DATA=\"../../asset_client/sample_data/sample_updates.json\" node ./scripts/run_sample_updates.js", 17 | "make-fish": "DATA=\"../../fish_client/sample_data/core_types.json\" node ./scripts/seed_core_types.js", 18 | "seed-sample-fish": "DATA=\"../../fish_client/sample_data/sample_data.json\" node ./scripts/seed_sample_data.js", 19 | "update-sample-fish": "DATA=\"../../fish_client/sample_data/sample_updates.json\" node ./scripts/run_sample_updates.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/hyperledger/sawtooth-supply-chain.git" 24 | }, 25 | "author": "", 26 | "license": "Apache-2.0", 27 | "bugs": { 28 | "url": "https://github.com/hyperledger/sawtooth-supply-chain/issues" 29 | }, 30 | "homepage": "https://github.com/hyperledger/sawtooth-supply-chain#readme", 31 | "dependencies": { 32 | "bcrypt": "^1.0.3", 33 | "body-parser": "^1.17.2", 34 | "express": "^4.15.4", 35 | "js-schema": "^1.0.1", 36 | "jsonwebtoken": "^7.4.3", 37 | "lodash": "^4.17.4", 38 | "protobufjs": "^6.8.0", 39 | "rethinkdb": "^2.3.3", 40 | "sawtooth-sdk": "^1.0.0-rc" 41 | }, 42 | "devDependencies": { 43 | "nodemon": "^1.11.0", 44 | "request": "^2.83.0", 45 | "request-promise-native": "^1.0.5", 46 | "standard": "^10.0.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/scripts/seed_core_types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const protos = require('../blockchain/protos') 20 | const { 21 | awaitServerPubkey, 22 | getTxnCreator, 23 | submitTxns, 24 | encodeTimestampedPayload 25 | } = require('../system/submit_utils') 26 | 27 | const DATA = process.env.DATA 28 | if (DATA.indexOf('.json') === -1) { 29 | throw new Error('Use the "DATA" environment variable to specify a JSON file') 30 | } 31 | 32 | const types = require(`./${DATA}`) 33 | 34 | protos.compile() 35 | .then(awaitServerPubkey) 36 | .then(batcherPublicKey => getTxnCreator(null, batcherPublicKey)) 37 | .then(createTxn => { 38 | const agentPayload = encodeTimestampedPayload({ 39 | action: protos.SCPayload.Action.CREATE_AGENT, 40 | createAgent: protos.CreateAgentAction.create({ 41 | name: 'Supply Chain Admin' 42 | }) 43 | }) 44 | 45 | const typePayloads = types.map(type => { 46 | return encodeTimestampedPayload({ 47 | action: protos.SCPayload.Action.CREATE_RECORD_TYPE, 48 | createRecordType: protos.CreateRecordTypeAction.create({ 49 | name: type.name, 50 | properties: type.properties.map(prop => { 51 | return protos.PropertySchema.create(prop) 52 | }) 53 | }) 54 | }) 55 | }) 56 | 57 | const txns = [ createTxn(agentPayload) ] 58 | .concat(typePayloads.map(payload => createTxn(payload))) 59 | return submitTxns(txns) 60 | }) 61 | .then(res => console.log('Types submitted:\n', JSON.parse(res))) 62 | .catch(err => { 63 | console.error(err.toString()) 64 | process.exit() 65 | }) 66 | -------------------------------------------------------------------------------- /server/system/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const fs = require('fs') 20 | const path = require('path') 21 | 22 | const loadConfig = (defaultValue = {}) => { 23 | try { 24 | return require('../config.json') 25 | } catch (err) { 26 | // Throw error on bad JSON, otherwise ignore 27 | if (err instanceof SyntaxError) throw err 28 | return {} 29 | } 30 | } 31 | 32 | const config = loadConfig() 33 | 34 | const initConfigValue = (key, defaultValue = null) => { 35 | config[key] = process.env[key] || config[key] || defaultValue 36 | } 37 | 38 | // Setup non-sensitive config variable with sensible defaults, 39 | // if not set in environment variables or config.json 40 | initConfigValue('PORT', 3000) 41 | initConfigValue('RETRY_WAIT', 5000) 42 | initConfigValue('DEFAULT_SUBMIT_WAIT', 5000000) 43 | initConfigValue('VALIDATOR_URL', 'tcp://localhost:4004') 44 | initConfigValue('DB_HOST', 'localhost') 45 | initConfigValue('DB_PORT', 28015) 46 | initConfigValue('DB_NAME', 'supply_chain') 47 | initConfigValue('SIGNING_ALGORITHM', 'secp256k1') 48 | 49 | // Setup config variables with no defaults 50 | initConfigValue('MAPS_API_KEY') 51 | 52 | // Setup sensitive variable, warning user if using defaults 53 | initConfigValue('JWT_SECRET') 54 | initConfigValue('PRIVATE_KEY') 55 | 56 | if (!config.PRIVATE_KEY) { 57 | config.PRIVATE_KEY = Array(64).fill('1').join('') 58 | console.warn( 59 | 'WARNING! No signing key provided. Batch signing will be insecure!') 60 | console.warn( 61 | 'Set "PRIVATE_KEY" as an environment variable or in "config.json" file.') 62 | } 63 | 64 | if (!config.JWT_SECRET) { 65 | config.JWT_SECRET = 'supply-chain-secret' 66 | console.warn( 67 | 'WARNING! No secret provided. JWT authorization tokens will be insecure!') 68 | console.warn( 69 | 'Set "JWT_SECRET" as an environment variable or in "config.json" file.') 70 | } 71 | 72 | // Config method to set a new value, then write it to config.json 73 | config.set = (key, value) => { 74 | config[key] = value 75 | 76 | const diskConfig = loadConfig() 77 | diskConfig[key] = value 78 | 79 | const configPath = path.resolve(__dirname, '../config.json') 80 | const jsonConfig = JSON.stringify(diskConfig, null, 2) 81 | fs.writeFile(configPath, jsonConfig, 'utf8', err => { 82 | if (err) console.error(err) 83 | }) 84 | } 85 | 86 | module.exports = config 87 | -------------------------------------------------------------------------------- /server/system/submit_utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Intel Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | 'use strict' 18 | 19 | const _ = require('lodash') 20 | const request = require('request-promise-native') 21 | const { createHash } = require('crypto') 22 | const secp256k1 = require('sawtooth-sdk/signing/secp256k1') 23 | const { 24 | Transaction, 25 | TransactionHeader, 26 | TransactionList 27 | } = require('sawtooth-sdk/protobuf') 28 | const protos = require('../blockchain/protos') 29 | 30 | const FAMILY_NAME = 'supply_chain' 31 | const FAMILY_VERSION = '1.1' 32 | const NAMESPACE = '3400de' 33 | 34 | const SERVER = process.env.SERVER || 'http://localhost:3000' 35 | const RETRY_WAIT = process.env.RETRY_WAIT || 5000 36 | 37 | const awaitServerInfo = () => { 38 | return request(`${SERVER}/info`) 39 | .catch(() => { 40 | console.warn( 41 | `Server unavailable, retrying in ${RETRY_WAIT / 1000} seconds...`) 42 | return new Promise(resolve => setTimeout(resolve, RETRY_WAIT)) 43 | .then(awaitServerInfo) 44 | }) 45 | } 46 | 47 | const awaitServerPubkey = () => { 48 | return awaitServerInfo().then(info => JSON.parse(info).pubkey) 49 | } 50 | 51 | const encodeHeader = (signerPublicKey, batcherPublicKey, payload) => { 52 | return TransactionHeader.encode({ 53 | signerPublicKey, 54 | batcherPublicKey, 55 | familyName: FAMILY_NAME, 56 | familyVersion: FAMILY_VERSION, 57 | inputs: [NAMESPACE], 58 | outputs: [NAMESPACE], 59 | nonce: (Math.random() * 10 ** 18).toString(36), 60 | payloadSha512: createHash('sha512').update(payload).digest('hex') 61 | }).finish() 62 | } 63 | 64 | const getTxnCreator = (privateKeyHex = null, batcherPublicKeyHex = null) => { 65 | const context = new secp256k1.Secp256k1Context() 66 | const privateKey = privateKeyHex === null 67 | ? context.newRandomPrivateKey() 68 | : secp256k1.Secp256k1PrivateKey.fromHex(privateKeyHex) 69 | 70 | const signerPublicKey = context.getPublicKey(privateKey).asHex() 71 | const batcherPublicKey = batcherPublicKeyHex === null 72 | ? signerPublicKey 73 | : batcherPublicKeyHex 74 | 75 | return payload => { 76 | const header = encodeHeader(signerPublicKey, batcherPublicKey, payload) 77 | const headerSignature = context.sign(header, privateKey) 78 | return Transaction.create({ header, headerSignature, payload }) 79 | } 80 | } 81 | 82 | const submitTxns = transactions => { 83 | return request({ 84 | method: 'POST', 85 | url: `${SERVER}/transactions?wait`, 86 | headers: { 'Content-Type': 'application/octet-stream' }, 87 | encoding: null, 88 | body: TransactionList.encode({ transactions }).finish() 89 | }) 90 | } 91 | 92 | const encodeTimestampedPayload = message => { 93 | return protos.SCPayload.encode(_.assign({ 94 | timestamp: Math.floor(Date.now() / 1000) 95 | }, message)).finish() 96 | } 97 | 98 | module.exports = { 99 | awaitServerPubkey, 100 | getTxnCreator, 101 | submitTxns, 102 | encodeTimestampedPayload 103 | } 104 | -------------------------------------------------------------------------------- /shell/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | FROM hyperledger/sawtooth-shell:nightly 17 | 18 | # Install Python, Node.js, and Ubuntu dependencies 19 | RUN echo "deb http://repo.sawtooth.me/ubuntu/1.0/stable bionic universe" >> /etc/apt/sources.list \ 20 | && (apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 44FC67F19B2466EA \ 21 | || apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 44FC67F19B2466EA) \ 22 | && apt-get update \ 23 | && apt-get install -y -q \ 24 | apt-transport-https \ 25 | build-essential \ 26 | ca-certificates \ 27 | curl \ 28 | libzmq3-dev \ 29 | pkg-config \ 30 | python3 \ 31 | python3-colorlog \ 32 | python3-dev \ 33 | python3-grpcio-tools \ 34 | python3-grpcio \ 35 | python3-nose2 \ 36 | python3-pip \ 37 | python3-protobuf \ 38 | python3-pytest-runner \ 39 | python3-pytest \ 40 | python3-sawtooth-sdk \ 41 | python3-sawtooth-signing \ 42 | python3-setuptools-scm \ 43 | python3-yaml \ 44 | software-properties-common \ 45 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 46 | && chmod 755 /tmp/setup-node.sh \ 47 | && /tmp/setup-node.sh \ 48 | && apt-get install nodejs -y -q \ 49 | && rm /tmp/setup-node.sh \ 50 | && apt-get clean \ 51 | && rm -rf /var/lib/apt/lists/* \ 52 | && npm install -g prebuild-install 53 | 54 | WORKDIR /sawtooth-supply-chain 55 | 56 | # Install NPM dependencies to central location, link to individual components 57 | COPY bin/splice_json bin/ 58 | COPY asset_client/package.json asset_client/ 59 | COPY fish_client/package.json fish_client/ 60 | COPY server/package.json server/ 61 | 62 | RUN \ 63 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 64 | http_proxy=$HTTP_PROXY; \ 65 | fi; \ 66 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 67 | https_proxy=$HTTPS_PROXY; \ 68 | fi; \ 69 | if [ ! -z $http_proxy ]; then \ 70 | npm config set proxy $http_proxy; \ 71 | fi; \ 72 | if [ ! -z $https_proxy ]; then \ 73 | npm config set https-proxy $https_proxy; \ 74 | fi 75 | 76 | RUN mkdir /node_deps \ 77 | && bin/splice_json \ 78 | asset_client/package.json \ 79 | fish_client/package.json \ 80 | server/package.json \ 81 | > /node_deps/package.json \ 82 | && cd /node_deps && npm install && cd - \ 83 | && ln -s /node_deps/node_modules asset_client/ \ 84 | && ln -s /node_deps/node_modules fish_client/ \ 85 | && ln -s /node_deps/node_modules server/ 86 | 87 | ENV PATH $PATH:/sawtooth-supply-chain/bin 88 | 89 | CMD ["tail", "-f", "/dev/null"] 90 | -------------------------------------------------------------------------------- /shell/Dockerfile-installed-bionic: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | FROM hyperledger/sawtooth-shell:nightly 17 | 18 | # Install Python, Node.js, and Ubuntu dependencies 19 | RUN echo "deb http://repo.sawtooth.me/ubuntu/nightly bionic universe" >> /etc/apt/sources.list \ 20 | && (apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 44FC67F19B2466EA \ 21 | || apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 44FC67F19B2466EA) \ 22 | && apt-get update \ 23 | && apt-get install -y -q \ 24 | apt-transport-https \ 25 | build-essential \ 26 | ca-certificates \ 27 | curl \ 28 | libzmq3-dev \ 29 | pkg-config \ 30 | python3 \ 31 | python3-colorlog \ 32 | python3-dev \ 33 | python3-grpcio-tools \ 34 | python3-grpcio \ 35 | python3-nose2 \ 36 | python3-pip \ 37 | python3-protobuf \ 38 | python3-pytest-runner \ 39 | python3-pytest \ 40 | python3-sawtooth-sdk \ 41 | python3-sawtooth-signing \ 42 | python3-setuptools-scm \ 43 | python3-yaml \ 44 | software-properties-common \ 45 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 46 | && chmod 755 /tmp/setup-node.sh \ 47 | && /tmp/setup-node.sh \ 48 | && apt-get install nodejs -y -q \ 49 | && rm /tmp/setup-node.sh \ 50 | && apt-get clean \ 51 | && rm -rf /var/lib/apt/lists/* \ 52 | && npm install -g prebuild-install 53 | 54 | WORKDIR /sawtooth-supply-chain 55 | 56 | # Install NPM dependencies to central location, link to individual components 57 | COPY bin/splice_json bin/ 58 | COPY asset_client/ asset_client/ 59 | COPY fish_client/ fish_client/ 60 | COPY protos/ protos/ 61 | COPY server/ server/ 62 | 63 | RUN \ 64 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 65 | http_proxy=$HTTP_PROXY; \ 66 | fi; \ 67 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 68 | https_proxy=$HTTPS_PROXY; \ 69 | fi; \ 70 | if [ ! -z $http_proxy ]; then \ 71 | npm config set proxy $http_proxy; \ 72 | fi; \ 73 | if [ ! -z $https_proxy ]; then \ 74 | npm config set https-proxy $https_proxy; \ 75 | fi 76 | 77 | 78 | RUN mkdir /node_deps \ 79 | && bin/splice_json \ 80 | asset_client/package.json \ 81 | fish_client/package.json \ 82 | server/package.json \ 83 | > /node_deps/package.json \ 84 | && cd /node_deps && npm install && cd - \ 85 | && ln -s /node_deps/node_modules asset_client/ \ 86 | && ln -s /node_deps/node_modules fish_client/ \ 87 | && ln -s /node_deps/node_modules server/ 88 | 89 | ENV PATH $PATH:/sawtooth-supply-chain/bin 90 | 91 | CMD ["tail", "-f", "/dev/null"] 92 | -------------------------------------------------------------------------------- /shell/Dockerfile-installed-xenial: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ------------------------------------------------------------------------------ 15 | 16 | FROM hyperledger/sawtooth-shell:1.0 17 | 18 | # Install Python, Node.js, and Ubuntu dependencies 19 | RUN echo "deb http://repo.sawtooth.me/ubuntu/1.0/stable xenial universe" >> /etc/apt/sources.list \ 20 | && (apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 8AA7AF1F1091A5FD \ 21 | || apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 8AA7AF1F1091A5FD) \ 22 | && apt-get update \ 23 | && apt-get install -y -q \ 24 | apt-transport-https \ 25 | build-essential \ 26 | ca-certificates \ 27 | curl \ 28 | libzmq3-dev \ 29 | pkg-config \ 30 | python3 \ 31 | python3-colorlog \ 32 | python3-dev \ 33 | python3-grpcio-tools=1.1.3-1 \ 34 | python3-grpcio=1.1.3-1 \ 35 | python3-nose2 \ 36 | python3-pip \ 37 | python3-protobuf \ 38 | python3-pytest-runner=2.6.2-1 \ 39 | python3-pytest=2.9.0-1 \ 40 | python3-sawtooth-sdk \ 41 | python3-sawtooth-signing \ 42 | python3-setuptools-scm=1.15.0-1 \ 43 | python3-yaml \ 44 | software-properties-common \ 45 | && curl -s -S -o /tmp/setup-node.sh https://deb.nodesource.com/setup_8.x \ 46 | && chmod 755 /tmp/setup-node.sh \ 47 | && /tmp/setup-node.sh \ 48 | && apt-get install nodejs -y -q \ 49 | && rm /tmp/setup-node.sh \ 50 | && apt-get clean \ 51 | && rm -rf /var/lib/apt/lists/* \ 52 | && npm install -g prebuild-install 53 | 54 | WORKDIR /sawtooth-supply-chain 55 | 56 | # Install NPM dependencies to central location, link to individual components 57 | COPY bin/splice_json bin/ 58 | COPY asset_client/ asset_client/ 59 | COPY fish_client/ fish_client/ 60 | COPY protos/ protos/ 61 | COPY server/ server/ 62 | 63 | RUN \ 64 | if [ ! -z $HTTP_PROXY ] && [ -z $http_proxy ]; then \ 65 | http_proxy=$HTTP_PROXY; \ 66 | fi; \ 67 | if [ ! -z $HTTPS_PROXY ] && [ -z $https_proxy ]; then \ 68 | https_proxy=$HTTPS_PROXY; \ 69 | fi; \ 70 | if [ ! -z $http_proxy ]; then \ 71 | npm config set proxy $http_proxy; \ 72 | fi; \ 73 | if [ ! -z $https_proxy ]; then \ 74 | npm config set https-proxy $https_proxy; \ 75 | fi 76 | 77 | 78 | RUN mkdir /node_deps \ 79 | && bin/splice_json \ 80 | asset_client/package.json \ 81 | fish_client/package.json \ 82 | server/package.json \ 83 | > /node_deps/package.json \ 84 | && cd /node_deps && npm install && cd - \ 85 | && ln -s /node_deps/node_modules asset_client/ \ 86 | && ln -s /node_deps/node_modules fish_client/ \ 87 | && ln -s /node_deps/node_modules server/ 88 | 89 | ENV PATH $PATH:/sawtooth-supply-chain/bin 90 | 91 | CMD ["tail", "-f", "/dev/null"] 92 | -------------------------------------------------------------------------------- /tests/sawtooth_sc_test/addressing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Intel Corporation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http:#www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ----------------------------------------------------------------------------- 15 | 16 | import hashlib 17 | 18 | 19 | def _hash(string): 20 | return hashlib.sha512(string.encode('utf-8')).hexdigest() 21 | 22 | 23 | # The first six characters of a T&T address are the first six 24 | # characters of the hash of the T&T family name. The next two 25 | # characters depend on the type of object being stored. There is no 26 | # formula for deriving these infixes. 27 | 28 | FAMILY_NAME = 'supply_chain' 29 | 30 | NAMESPACE = _hash(FAMILY_NAME)[:6] 31 | 32 | AGENT = 'ae' 33 | PROPERTY = 'ea' 34 | PROPOSAL = 'aa' 35 | RECORD = 'ec' 36 | RECORD_TYPE = 'ee' 37 | 38 | 39 | def make_agent_address(identifier): 40 | return ( 41 | NAMESPACE 42 | + AGENT 43 | + _hash(identifier)[:62] 44 | ) 45 | 46 | 47 | def make_record_address(record_id): 48 | return ( 49 | NAMESPACE 50 | + RECORD 51 | + _hash(record_id)[:62] 52 | ) 53 | 54 | 55 | def make_record_type_address(type_name): 56 | return ( 57 | NAMESPACE 58 | + RECORD_TYPE 59 | + _hash(type_name)[:62] 60 | ) 61 | 62 | 63 | RECORD_TYPE_ADDRESS_RANGE = NAMESPACE + RECORD_TYPE 64 | 65 | 66 | def make_property_address(record_id, property_name, page=0): 67 | return ( 68 | make_property_address_range(record_id) 69 | + _hash(property_name)[:22] 70 | + _num_to_page_number(page) 71 | ) 72 | 73 | 74 | def _num_to_page_number(num): 75 | return hex(num)[2:].zfill(4) 76 | 77 | 78 | def make_property_address_range(record_id): 79 | return ( 80 | NAMESPACE 81 | + PROPERTY 82 | + _hash(record_id)[:36] 83 | ) 84 | 85 | 86 | def make_proposal_address(record_id, agent_id): 87 | return ( 88 | NAMESPACE 89 | + PROPOSAL 90 | + _hash(record_id)[:36] 91 | + _hash(agent_id)[:26] 92 | ) 93 | --------------------------------------------------------------------------------