├── .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 | {% trans %}Transaction Family Specification{% endtrans %}
8 | {% trans %}learn about Hyperledger Sawtooth Supply Chain{% endtrans %}
9 |
10 | {% trans %}Table of Contents{% endtrans %}
11 | {% trans %}detailed list of documentation sections{% endtrans %}
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 |
--------------------------------------------------------------------------------