├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── application.rs ├── bin │ └── synchronicity │ │ └── main.rs ├── commands.rs ├── commands │ ├── init.rs │ ├── start.rs │ └── version.rs ├── config.rs ├── error.rs ├── executor.rs ├── lib.rs ├── prelude.rs └── verifier.rs ├── synchro ├── Cargo.toml ├── README.md └── src │ ├── config.rs │ ├── config │ ├── builder.rs │ ├── key_seed.rs │ └── peer_info.rs │ ├── crypto.rs │ ├── error.rs │ ├── launcher.rs │ ├── lib.rs │ ├── node.rs │ └── transaction.rs └── tests └── acceptance.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: develop 7 | 8 | name: Rust 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v1 17 | 18 | - name: Install stable toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | override: true 23 | 24 | - name: Run cargo check 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: check 28 | 29 | test: 30 | name: Test Suite 31 | strategy: 32 | matrix: 33 | platform: 34 | - ubuntu-latest 35 | - macos-latest 36 | toolchain: 37 | - 1.39.0 38 | - stable 39 | runs-on: ${{ matrix.platform }} 40 | steps: 41 | - name: Checkout sources 42 | uses: actions/checkout@v1 43 | 44 | - name: Cache cargo registry 45 | uses: actions/cache@v1 46 | with: 47 | path: ~/.cargo/registry 48 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 49 | 50 | - name: Cache cargo index 51 | uses: actions/cache@v1 52 | with: 53 | path: ~/.cargo/git 54 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 55 | 56 | - name: Cache cargo build 57 | uses: actions/cache@v1 58 | with: 59 | path: target 60 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('Cargo.lock') }} 61 | 62 | - name: Install stable toolchain 63 | uses: actions-rs/toolchain@v1 64 | with: 65 | toolchain: ${{ matrix.toolchain }} 66 | override: true 67 | 68 | - name: Run cargo test 69 | uses: actions-rs/cargo@v1 70 | env: 71 | RUSTFLAGS: -D warnings 72 | with: 73 | command: test 74 | args: --all --release 75 | 76 | fmt: 77 | name: Rustfmt 78 | runs-on: ubuntu-latest 79 | steps: 80 | - name: Checkout sources 81 | uses: actions/checkout@v1 82 | 83 | - name: Install stable toolchain 84 | uses: actions-rs/toolchain@v1 85 | with: 86 | toolchain: stable 87 | override: true 88 | 89 | - name: Install rustfmt 90 | run: rustup component add rustfmt 91 | 92 | - name: Run cargo fmt 93 | uses: actions-rs/cargo@v1 94 | with: 95 | command: fmt 96 | args: --all -- --check 97 | 98 | clippy: 99 | name: Clippy 100 | runs-on: ubuntu-latest 101 | steps: 102 | - name: Checkout sources 103 | uses: actions/checkout@v1 104 | 105 | - name: Cache cargo registry 106 | uses: actions/cache@v1 107 | with: 108 | path: ~/.cargo/registry 109 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 110 | 111 | - name: Cache cargo index 112 | uses: actions/cache@v1 113 | with: 114 | path: ~/.cargo/git 115 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 116 | 117 | - name: Cache cargo build 118 | uses: actions/cache@v1 119 | with: 120 | path: target 121 | key: ${{ runner.os }}-cargo-build-clippy-${{ hashFiles('Cargo.lock') }} 122 | 123 | - name: Install stable toolchain 124 | uses: actions-rs/toolchain@v1 125 | with: 126 | toolchain: stable 127 | override: true 128 | 129 | - name: Install clippy 130 | run: rustup component add clippy 131 | 132 | - name: Run cargo clippy 133 | uses: actions-rs/cargo@v1 134 | with: 135 | command: clippy 136 | args: --all -- -D warnings 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io). 59 | All complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synchronicity" 3 | version = "0.0.1" 4 | description = """ 5 | Distributed build system providing cryptographic proofs-of-reproducibility 6 | via BFT consensus. 7 | """ 8 | authors = ["Tony Arcieri "] 9 | repository = "https://github.com/iqlusioninc/synchronicity" 10 | edition = "2018" 11 | license = "Apache-2.0" 12 | readme = "README.md" 13 | categories = ["command-line-utilities", "development-tools::build-utils", "rust-patterns"] 14 | keywords = ["bft", "libra", "reproducible-builds"] 15 | 16 | [workspace] 17 | members = [".", "synchro"] 18 | 19 | [badges] 20 | maintenance = { status = "experimental" } 21 | 22 | [dependencies] 23 | abscissa_core = "0.5" 24 | gumdrop = "0.7" 25 | serde = { version = "1", features = ["serde_derive"] } 26 | synchro = { version = "0.1", path = "synchro" } 27 | thiserror = "1" 28 | 29 | [dev-dependencies] 30 | tempfile = "3" 31 | 32 | [dev-dependencies.abscissa_core] 33 | version = "0.5" 34 | features = ["testing"] 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Synchronicity iqlusion 2 | 3 | [![Build Status][build-image]][build-link] 4 | [![Safety Dance][safety-image]][safety-link] 5 | ![MSRV][msrv-image] 6 | [![Apache 2.0 Licensed][license-image]][license-link] 7 | [![Gitter Chat][gitter-image]][gitter-link] 8 | 9 | > A connecting principle linked to the invisible. 10 | > Almost imperceptible. Something inexpressible. 11 | 12 | —The Police, *Synchronicity I* 13 | 14 | Distributed build system providing cryptographic proofs-of-reproducibility 15 | via Byzantine Fault Tolerant (BFT) consensus. 16 | 17 | [Documentation](https://bitly.com/98K8eH) 18 | 19 | ## About 20 | 21 | **Synchronicity** is a distributed build system for Rust crates which have 22 | been published to [crates.io]. It builds crates reproducibly inside of Docker 23 | containers managed using [Rustwide], the core library behind tools like 24 | [Crater] and [docs.rs]. 25 | 26 | ### Goal 27 | 28 | The goal of **Synchronicity** is to provide a distributed 29 | [binary transparency] (BT) system for Rust crates which is independent of 30 | any one central operator. BT systems checkpoint content hashes of binaries 31 | in an append-only log which, if nothing else, ensures that forensic evidence 32 | of all builds is logged in a fairly permanent way. 33 | 34 | This is helpful in situations where it would be desirable to use pre-built 35 | binaries of crates, such as distributed build caches. In such a situation, 36 | a binary transparency system can ensure that the artifact one receives is 37 | the same as everyone else is receiving in a way that can't be easily 38 | altered by an attacker. 39 | 40 | By using a system based on reproducible builds, **Synchronicity** is also 41 | able to provide high confidence that binary artifacts are faithful to their 42 | original source code using cryptographic proofs that are easy to obtain 43 | and verify (even by an offline party/process). This helps prevent a malicious 44 | builder from inserting trojans in the source code prior to performing a 45 | build (or at least, ideally makes it much more difficult). 46 | 47 | ### Operational Details 48 | 49 | Builders running **Synchronicity** also run a BFT consensus algorithm between 50 | each other (as part of a closed, "permissioned" group), and in doing so come to 51 | agreement on whether or not a build was successfully reproduced by a threshold 52 | of the group's members. Consensus is provided by [Libra's HotStuff BFT][hotstuff]. 53 | 54 | Any builder can submit a build to be run by the rest of the group. The results 55 | of the build are then published as part of a commit-and-reveal scheme. 56 | After all builders have completed the build, or a timeout is reached, the 57 | builders reveal hashes identifying the build artifacts, and if a threshold 58 | of them match, evidence thereof is stored in an append-only Merkle log 59 | generated by the consensus group. 60 | 61 | Once evidence of a successful build reproduction has been published in the log, 62 | clients interested in determining if they should trust a particular build can 63 | request cryptographic proof-of-inclusion that it has been successfully 64 | reproduced. So long as a threshold of the group does not collude to publish 65 | fraudulent reproducibility results, this cryptographic proof can be trusted 66 | as evidence that a build with a matching hash is reproducible from the 67 | original source code published on [crates.io]. 68 | 69 | Cryptographic proofs of reproducibility are static artifacts that can be 70 | obtained once and included along with a build, ensuring privacy for 71 | verifiers who do not want to reveal to a central service which proofs they 72 | are verifying. 73 | 74 | Verification can be performed offline by consumers of binary artifacts. 75 | Proofs can be passed as static strings/files (or potentially included 76 | into the binary artifacts themselves) and verified offline by the actual 77 | build workers. 78 | 79 | ## Status 80 | 81 | **Synchronicity** is a work-in-progress and at an early stage of development 82 | and is not ready to be used. Check back later! 83 | 84 | ## Minimum Supported Rust Version 85 | 86 | - Rust **1.39+** 87 | 88 | ## Code of Conduct 89 | 90 | We abide by the [Contributor Covenant][cc-md] and ask that you do as well. 91 | 92 | For more information, please see [CODE_OF_CONDUCT.md][cc-md]. 93 | 94 | ## License 95 | 96 | Copyright © 2019 iqlusion 97 | 98 | Licensed under the Apache License, Version 2.0 (the "License"); 99 | you may not use this file except in compliance with the License. 100 | You may obtain a copy of the License at 101 | 102 | https://www.apache.org/licenses/LICENSE-2.0 103 | 104 | Unless required by applicable law or agreed to in writing, software 105 | distributed under the License is distributed on an "AS IS" BASIS, 106 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 107 | See the License for the specific language governing permissions and 108 | limitations under the License. 109 | 110 | ## Contribution 111 | 112 | Unless you explicitly state otherwise, any contribution intentionally 113 | submitted for inclusion in the work by you shall be licensed as above, 114 | without any additional terms or conditions. 115 | 116 | [//]: # (badges) 117 | 118 | [build-image]: https://github.com/iqlusioninc/synchronicity/workflows/Rust/badge.svg?branch=develop&event=push 119 | [build-link]: https://github.com/iqlusioninc/synchronicity/actions 120 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 121 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 122 | [msrv-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg 123 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 124 | [license-link]: https://github.com/iqlusioninc/synchronicity/blob/master/LICENSE 125 | [gitter-image]: https://badges.gitter.im/badge.svg 126 | [gitter-link]: https://gitter.im/iqlusioninc/community 127 | 128 | [//]: # (general links) 129 | 130 | [crates.io]: https://crates.io 131 | [Rustwide]: https://github.com/rust-lang/rustwide 132 | [Crater]: https://github.com/rust-lang/crater 133 | [docs.rs]: https://docs.rs/about 134 | [binary transparency]: https://wiki.mozilla.org/Security/Binary_Transparency 135 | [hotstuff]: https://github.com/libra/libra/tree/master/consensus 136 | [cc-web]: https://contributor-covenant.org/ 137 | [cc-md]: https://github.com/iqlusioninc/synchronicity/blob/develop/CODE_OF_CONDUCT.md 138 | -------------------------------------------------------------------------------- /src/application.rs: -------------------------------------------------------------------------------- 1 | //! Synchronicity Abscissa Application 2 | 3 | use crate::{commands::SynchronicityCmd, config::SynchronicityConfig}; 4 | use abscissa_core::{ 5 | application::{self, AppCell}, 6 | config, trace, Application, EntryPoint, FrameworkError, StandardPaths, 7 | }; 8 | 9 | /// Application state 10 | pub static APPLICATION: AppCell = AppCell::new(); 11 | 12 | /// App state reader 13 | pub fn app_reader() -> application::lock::Reader { 14 | APPLICATION.read() 15 | } 16 | 17 | /// App state writer 18 | pub fn app_writer() -> application::lock::Writer { 19 | APPLICATION.write() 20 | } 21 | 22 | /// App config reader 23 | pub fn app_config() -> config::Reader { 24 | config::Reader::new(&APPLICATION) 25 | } 26 | 27 | /// Synchronicity Application 28 | #[derive(Debug)] 29 | pub struct SynchronicityApp { 30 | /// Application configuration. 31 | config: Option, 32 | 33 | /// Application state. 34 | state: application::State, 35 | } 36 | 37 | /// Initialize a new application instance. 38 | impl Default for SynchronicityApp { 39 | fn default() -> Self { 40 | Self { 41 | config: None, 42 | state: application::State::default(), 43 | } 44 | } 45 | } 46 | 47 | impl Application for SynchronicityApp { 48 | /// Entrypoint command for this application. 49 | type Cmd = EntryPoint; 50 | 51 | /// Application configuration. 52 | type Cfg = SynchronicityConfig; 53 | 54 | /// Paths to resources within the application. 55 | type Paths = StandardPaths; 56 | 57 | /// Accessor for application configuration. 58 | fn config(&self) -> &SynchronicityConfig { 59 | self.config.as_ref().expect("config not loaded") 60 | } 61 | 62 | /// Borrow the application state immutably. 63 | fn state(&self) -> &application::State { 64 | &self.state 65 | } 66 | 67 | /// Borrow the application state mutably. 68 | fn state_mut(&mut self) -> &mut application::State { 69 | &mut self.state 70 | } 71 | 72 | /// Register all components used by this application. 73 | fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { 74 | let components = self.framework_components(command)?; 75 | self.state.components.register(components) 76 | } 77 | 78 | /// Post-configuration lifecycle callback. 79 | fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> { 80 | // Configure components 81 | self.state.components.after_config(&config)?; 82 | self.config = Some(config); 83 | Ok(()) 84 | } 85 | 86 | /// Get tracing configuration from command-line options 87 | fn tracing_config(&self, command: &EntryPoint) -> trace::Config { 88 | if command.verbose { 89 | trace::Config::verbose() 90 | } else { 91 | trace::Config::default() 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/bin/synchronicity/main.rs: -------------------------------------------------------------------------------- 1 | //! Main entry point for Synchronicity 2 | 3 | #![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] 4 | #![forbid(unsafe_code)] 5 | 6 | use synchronicity::application::APPLICATION; 7 | 8 | /// Boot Synchronicity 9 | fn main() { 10 | abscissa_core::boot(&APPLICATION); 11 | } 12 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | //! Synchronicity Subcommands 2 | 3 | mod init; 4 | mod start; 5 | mod version; 6 | 7 | use self::{init::InitCmd, start::StartCmd, version::VersionCmd}; 8 | use crate::config::{SynchronicityConfig, CONFIG_FILE}; 9 | use abscissa_core::{Command, Configurable, Help, Options, Runnable}; 10 | use std::path::PathBuf; 11 | 12 | /// Synchronicity Subcommands 13 | #[derive(Command, Debug, Options, Runnable)] 14 | pub enum SynchronicityCmd { 15 | /// The `help` subcommand 16 | #[options(help = "get usage information")] 17 | Help(Help), 18 | 19 | /// The `version` subcommand 20 | #[options(help = "display version information")] 21 | Version(VersionCmd), 22 | 23 | /// The `init` subcommand 24 | #[options(help = "initialize application home/config")] 25 | Init(InitCmd), 26 | 27 | /// The `start` subcommand 28 | #[options(help = "start the application")] 29 | Start(StartCmd), 30 | } 31 | 32 | impl Configurable for SynchronicityCmd { 33 | /// Location of the configuration file 34 | fn config_path(&self) -> Option { 35 | // Check if the config file exists, and if it does not, ignore it. 36 | let filename = PathBuf::from(CONFIG_FILE); 37 | 38 | if filename.exists() { 39 | Some(filename) 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/init.rs: -------------------------------------------------------------------------------- 1 | //! `init` subcommand 2 | 3 | use crate::{config::CONFIG_FILE, prelude::*}; 4 | use abscissa_core::{Command, Options, Runnable}; 5 | use std::{ 6 | fs, 7 | path::{Path, PathBuf}, 8 | process::exit, 9 | }; 10 | use synchro::config::{self, peer_info, KeySeed}; 11 | 12 | /// Derivation component used when computing seed 13 | pub const DERIVATION_COMPONENT: &[u8] = b"synchronicity"; 14 | 15 | /// `init` subcommand 16 | #[derive(Command, Debug, Options)] 17 | pub struct InitCmd { 18 | /// Listen address to bind to (default 0.0.0.0) 19 | #[options(short = "l", long = "listen", help = "listen on this IP address")] 20 | listen_address: Option, 21 | 22 | /// Path to the base directory 23 | #[options(free)] 24 | base_dir: Vec, 25 | } 26 | 27 | impl Runnable for InitCmd { 28 | /// Initialize application configuration. 29 | fn run(&self) { 30 | let base_dir = self.prepare_base_dir(); 31 | 32 | // TODO(tarcieri): support for reusing a previously generated `KeySeed` 33 | let key_seed = KeySeed::generate(); 34 | 35 | self.generate_synchronicity_toml(&base_dir); 36 | self.generate_libra_configs(&base_dir, key_seed); 37 | } 38 | } 39 | 40 | impl InitCmd { 41 | /// Ensure the base directory exists 42 | pub fn prepare_base_dir(&self) -> PathBuf { 43 | if self.base_dir.len() != 1 { 44 | status_err!("usage: synchronicity init /path/to/syncronicity/base/dir"); 45 | exit(1); 46 | } 47 | 48 | let base_dir = self.base_dir[0].canonicalize().unwrap_or_else(|e| { 49 | status_err!("couldn't canonicalize base dir: {}", e); 50 | exit(1); 51 | }); 52 | 53 | // Create the scratch dir, which will ensure the parent also exists 54 | fs::create_dir_all(base_dir.join("scratch")).unwrap_or_else(|e| { 55 | status_err!("couldn't create base dir: {}", e); 56 | exit(1); 57 | }); 58 | 59 | base_dir 60 | } 61 | 62 | /// Generate Synchronicity-specific config file (i.e. `synchronicity.toml`) 63 | pub fn generate_synchronicity_toml(&self, base_dir: &Path) { 64 | // TODO(tarcieri): better templating 65 | let config_data = format!( 66 | "# serendipity.toml: configuration file for Serendipity\n\ 67 | node_config = \"{}\"\n\ 68 | scratch_dir = \"{}\"\n\ 69 | ", 70 | base_dir.join("node.config.toml").display(), 71 | base_dir.join("scratch/").display() 72 | ); 73 | 74 | let config_path = base_dir.join(CONFIG_FILE); 75 | 76 | fs::write(&config_path, config_data).unwrap_or_else(|e| { 77 | status_err!("couldn't write {}: {}", config_path.display(), e); 78 | exit(1); 79 | }); 80 | 81 | status_ok!("Generated", "{}", config_path.display()); 82 | } 83 | 84 | /// Generate configuration files specific to Libra 85 | pub fn generate_libra_configs(&self, base_dir: &Path, key_seed: KeySeed) { 86 | let mut builder = config::Builder::new(key_seed); 87 | builder.with_output_dir(base_dir); 88 | 89 | if let Some(listen_addr) = &self.listen_address { 90 | builder.with_listen_address(listen_addr); 91 | } 92 | 93 | // Generate private keys as well as consensus and network configs 94 | let (private_keys, consensus_peers_config, network_peers_config) = 95 | builder.generate_keys_and_configs(DERIVATION_COMPONENT, 0); 96 | 97 | let peer_id = network_peers_config.peers.keys().next().unwrap().to_owned(); 98 | 99 | builder.generate_peer_info(&peer_id, consensus_peers_config, network_peers_config); 100 | status_ok!( 101 | "Generated", 102 | "{}", 103 | base_dir.join(peer_info::DEFAULT_FILENAME).display() 104 | ); 105 | 106 | let (_account, (consensus_private_key, network_private_keys)) = 107 | private_keys.into_iter().next().unwrap(); 108 | 109 | let consensus_config = builder.generate_consensus_config(consensus_private_key); 110 | status_ok!( 111 | "Generated", 112 | "{}", 113 | base_dir 114 | .join(&consensus_config.consensus_keypair_file) 115 | .display() 116 | ); 117 | 118 | let network_config = builder.generate_network_config(&peer_id, network_private_keys); 119 | status_ok!( 120 | "Generated", 121 | "{}", 122 | base_dir 123 | .join(&network_config.network_keypairs_file) 124 | .display() 125 | ); 126 | 127 | builder.generate_node_config(consensus_config, network_config); 128 | status_ok!( 129 | "Generated", 130 | "{}", 131 | base_dir.join("node.config.toml").display() 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/commands/start.rs: -------------------------------------------------------------------------------- 1 | //! `start` subcommand - launch Synchronicity node 2 | 3 | // Parts adapted from upstream Libra's `main_node.rs`: 4 | // 5 | // 6 | // Copyright (c) The Libra Core Contributors 7 | 8 | use crate::{executor::SynchronicityExecutor, prelude::*, verifier::VerifyProvider}; 9 | use abscissa_core::{Command, Options, Runnable}; 10 | use synchro::{config::NodeConfig, Launcher, Node}; 11 | 12 | /// `start` subcommand 13 | #[derive(Command, Debug, Options)] 14 | pub struct StartCmd {} 15 | 16 | impl Runnable for StartCmd { 17 | /// Start the application. 18 | fn run(&self) { 19 | let verify_provider = VerifyProvider::new(); 20 | let launcher = Launcher::new(self.load_node_config(), verify_provider).unwrap(); 21 | let _node: Node = launcher.launch().unwrap(); 22 | } 23 | } 24 | 25 | impl StartCmd { 26 | /// Load the `NodeConfig` 27 | fn load_node_config(&self) -> NodeConfig { 28 | let cfg = app_config(); 29 | cfg.load_node_config() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/version.rs: -------------------------------------------------------------------------------- 1 | //! `version` subcommand 2 | 3 | #![allow(clippy::never_loop)] 4 | 5 | use super::SynchronicityCmd; 6 | use abscissa_core::{Command, Options, Runnable}; 7 | 8 | /// `version` subcommand 9 | #[derive(Command, Debug, Default, Options)] 10 | pub struct VersionCmd {} 11 | 12 | impl Runnable for VersionCmd { 13 | /// Print version message 14 | fn run(&self) { 15 | println!( 16 | "{} {}", 17 | SynchronicityCmd::name(), 18 | SynchronicityCmd::version() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Synchronicity Configuration 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use std::path::PathBuf; 5 | use synchro::config::{NodeConfig, PersistableConfig}; 6 | 7 | /// Synchronicity Configuration Filename 8 | pub const CONFIG_FILE: &str = "synchronicity.toml"; 9 | 10 | /// Synchronicity Configuration 11 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 12 | #[serde(deny_unknown_fields)] 13 | pub struct SynchronicityConfig { 14 | /// Node config directory 15 | pub node_config: PathBuf, 16 | 17 | /// Scratch directory 18 | pub scratch_dir: PathBuf, 19 | } 20 | 21 | impl SynchronicityConfig { 22 | /// Load [`NodeConfig`] from the configured location 23 | pub fn load_node_config(&self) -> NodeConfig { 24 | NodeConfig::load_config(&self.node_config) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | 3 | use abscissa_core::error::{BoxError, Context}; 4 | use std::{ 5 | fmt::{self, Display}, 6 | io, 7 | ops::Deref, 8 | }; 9 | use thiserror::Error; 10 | 11 | /// Kinds of errors 12 | #[derive(Copy, Clone, Eq, Error, PartialEq, Debug)] 13 | pub enum ErrorKind { 14 | /// Error in configuration file 15 | #[error("config error")] 16 | Config, 17 | 18 | /// Input/output error 19 | #[error("I/O error")] 20 | Io, 21 | } 22 | 23 | impl ErrorKind { 24 | /// Create an error context from this error 25 | pub fn context(self, source: impl Into) -> Context { 26 | Context::new(self, Some(source.into())) 27 | } 28 | } 29 | 30 | /// Error type 31 | #[derive(Debug)] 32 | pub struct Error(Box>); 33 | 34 | impl Deref for Error { 35 | type Target = Context; 36 | 37 | fn deref(&self) -> &Context { 38 | &self.0 39 | } 40 | } 41 | 42 | impl Display for Error { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | self.0.fmt(f) 45 | } 46 | } 47 | 48 | impl From> for Error { 49 | fn from(context: Context) -> Self { 50 | Error(Box::new(context)) 51 | } 52 | } 53 | 54 | impl From for Error { 55 | fn from(other: io::Error) -> Self { 56 | ErrorKind::Io.context(other).into() 57 | } 58 | } 59 | 60 | impl std::error::Error for Error { 61 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 62 | self.0.source() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | //! Synchronicity state machine executor 2 | 3 | use synchro::{ 4 | config::VMConfig, 5 | error::Error, 6 | state_view::StateView, 7 | transaction::{SignedTransaction, Status, Transaction, TransactionOutput}, 8 | vm_runtime::{VMExecutor, VMVerifier}, 9 | }; 10 | 11 | /// State machine executor used by Synchronicity 12 | pub struct SynchronicityExecutor {} 13 | 14 | impl SynchronicityExecutor { 15 | /// Create a new SynchronicityExecutor (stub!) 16 | #[allow(clippy::new_without_default)] // sate clippy, for now 17 | pub fn new() -> Self { 18 | Self {} 19 | } 20 | } 21 | 22 | impl VMExecutor for SynchronicityExecutor { 23 | fn execute_block( 24 | _transactions: Vec, 25 | _config: &VMConfig, 26 | _state_view: &dyn StateView, 27 | ) -> Result, Error> { 28 | // TODO(tarcieri): implement this! 29 | unimplemented!(); 30 | } 31 | } 32 | 33 | impl VMVerifier for SynchronicityExecutor { 34 | fn validate_transaction( 35 | &self, 36 | _transaction: SignedTransaction, 37 | _state_view: &dyn StateView, 38 | ) -> Option { 39 | // TODO(tarcieri): implement this! 40 | unimplemented!(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Synchronicity 2 | //! 3 | //! > A connecting principle linked to the invisible. 4 | //! > Almost imperceptible. Something inexpressible. 5 | //! 6 | //! —The Police, *Synchronicity I* 7 | //! 8 | //! Distributed build system providing cryptographic proofs-of-reproducibility 9 | //! via Byzantine Fault Tolerant (BFT) consensus. 10 | //! 11 | //! [Documentation](https://bitly.com/98K8eH) 12 | //! 13 | //! ## About 14 | //! 15 | //! **Synchronicity** is a distributed build system for Rust crates which have 16 | //! been published to [crates.io]. It builds crates reproducibly inside of Docker 17 | //! containers managed using [Rustwide], the core library behind tools like 18 | //! [Crater] and [docs.rs]. 19 | //! 20 | //! Builders running **Synchronicity** also run a BFT consensus algorithm between 21 | //! each other (as part of a closed, "permissioned" group), and in doing so come to 22 | //! agreement on whether or not a build was successfully reproduced by a threshold 23 | //! of the group. Consensus is provided by [Libra's HotStuff BFT][hotstuff]. 24 | //! 25 | //! Any builder can submit a build to be run by the rest of the group. The results 26 | //! of the build are then published as part of a commit-and-reveal scheme. 27 | //! After all builders have completed the build, or a timeout is reached, the 28 | //! builders reveal hashes identifying the build artifacts, and if a threshold 29 | //! of them match, evidence thereof is stored in an append-only Merkle log 30 | //! generated by the consensus group. 31 | //! 32 | //! Once evidence of a successful build reproduction has been published in the log, 33 | //! clients interested in determining if they should trust a particular build can 34 | //! request cryptographic proof-of-inclusion that it has been successfully 35 | //! reproduced. So long as a threshold of the group does not collude to publish 36 | //! fraudulent reproducibility results, this cryptographic proof can be trusted 37 | //! as evidence that a build with a matching hash is reproducible from the 38 | //! original source code published on [crates.io]. 39 | //! 40 | //! [crates.io]: https://crates.io 41 | //! [Rustwide]: https://github.com/rust-lang/rustwide 42 | //! [Crater]: https://github.com/rust-lang/crater 43 | //! [docs.rs]: https://docs.rs/about 44 | //! [hotstuff]: https://github.com/libra/libra/tree/master/consensus 45 | 46 | #![forbid(unsafe_code)] 47 | #![warn(rust_2018_idioms, unused_lifetimes, unused_qualifications)] 48 | 49 | pub mod application; 50 | pub mod commands; 51 | pub mod config; 52 | pub mod error; 53 | pub mod executor; 54 | pub mod prelude; 55 | pub mod verifier; 56 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Application-local prelude: conveniently import types/functions/macros 2 | //! which are generally useful and should be available everywhere. 3 | 4 | /// Abscissa core prelude 5 | pub use abscissa_core::prelude::*; 6 | 7 | /// Application state accessors 8 | pub use crate::application::{app_config, app_reader, app_writer}; 9 | -------------------------------------------------------------------------------- /src/verifier.rs: -------------------------------------------------------------------------------- 1 | //! Transaction verifier 2 | 3 | use crate::executor::SynchronicityExecutor; 4 | use std::sync::Arc; 5 | use synchro::{ 6 | error::Error, 7 | futures::future::Future, 8 | storage_client::StorageRead, 9 | transaction::{NewVerifier, SignedTransaction, Status, TransactionValidation}, 10 | }; 11 | 12 | /// Verification provider 13 | pub struct VerifyProvider {} 14 | 15 | impl VerifyProvider { 16 | /// Create a new verify provider 17 | #[allow(clippy::new_without_default)] // sate clippy, for now 18 | pub fn new() -> Self { 19 | Self {} 20 | } 21 | } 22 | 23 | impl NewVerifier for VerifyProvider { 24 | type Verifier = Verifier; 25 | 26 | /// Initialize a transaction validator from the given storage reader 27 | fn new_verifier(&self, storage_read_client: Arc) -> Verifier { 28 | Verifier::new(storage_read_client) 29 | } 30 | } 31 | 32 | /// Validator for Synchronicity transactions 33 | #[allow(dead_code)] // TODO(tarcieri): verify stuff 34 | pub struct Verifier { 35 | storage_read_client: Arc, 36 | executor: SynchronicityExecutor, 37 | } 38 | 39 | impl Verifier { 40 | pub fn new(storage_read_client: Arc) -> Self { 41 | // TODO(tarcieri): make this real 42 | let executor = SynchronicityExecutor::new(); 43 | 44 | Self { 45 | storage_read_client, 46 | executor, 47 | } 48 | } 49 | } 50 | 51 | impl TransactionValidation for Verifier { 52 | type ValidationInstance = SynchronicityExecutor; 53 | 54 | /// Validate transaction. See example here for how it's supposed to work: 55 | /// 56 | /// 57 | fn validate_transaction( 58 | &self, 59 | _txn: SignedTransaction, 60 | ) -> Box, Error = Error> + Send> { 61 | unimplemented!(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /synchro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synchro" 3 | description = """ 4 | Byzantine Fault Tolerant consensus library built on HotStuff BFT 5 | used by the Synchronicity reproducible build system 6 | """ 7 | authors = ["Tony Arcieri "] 8 | version = "0.1.0" 9 | homepage = "https://github.com/iqlusioninc/synchronicity" 10 | repository = "https://github.com/iqlusioninc/synchronicity/tree/develop/synchro" 11 | edition = "2018" 12 | license = "Apache-2.0" 13 | readme = "README.md" 14 | categories = ["algorithms", "api-bindings", "asynchronous", "network-programming"] 15 | keywords = ["bft", "consensus", "hotstuff", "libra"] 16 | 17 | [badges] 18 | maintenance = { status = "experimental" } 19 | 20 | [dependencies] 21 | futures = "0.1.28" 22 | grpcio = { version = "=0.5.0-alpha.4", default-features = false } 23 | hkd32 = { version = "0.3", features = ["mnemonic"] } 24 | log = "0.4" 25 | parity-multiaddr = { version = "0.5", default-features = false } 26 | serde = { version = "1", features = ["serde_derive"] } 27 | tokio = "0.2.0-alpha.6" 28 | 29 | # 30 | # Libra core dependencies 31 | # 32 | 33 | [dependencies.libra-config] 34 | git = "https://github.com/iqlusioninc/libra.git" 35 | branch = "synchro" 36 | 37 | [dependencies.libra-crypto] 38 | git = "https://github.com/iqlusioninc/libra.git" 39 | branch = "synchro" 40 | 41 | [dependencies.libra-failure-ext] 42 | git = "https://github.com/iqlusioninc/libra.git" 43 | branch = "synchro" 44 | 45 | [dependencies.libra-mempool] 46 | git = "https://github.com/iqlusioninc/libra.git" 47 | branch = "synchro" 48 | 49 | [dependencies.libra-state-view] 50 | git = "https://github.com/iqlusioninc/libra.git" 51 | branch = "synchro" 52 | 53 | [dependencies.libra-types] 54 | git = "https://github.com/iqlusioninc/libra.git" 55 | branch = "synchro" 56 | 57 | # Non-`libra` prefixed Libra core dependencies 58 | 59 | [dependencies.consensus] 60 | git = "https://github.com/iqlusioninc/libra.git" 61 | branch = "synchro" 62 | 63 | [dependencies.executor] 64 | git = "https://github.com/iqlusioninc/libra.git" 65 | branch = "synchro" 66 | 67 | [dependencies.grpc-helpers] 68 | git = "https://github.com/iqlusioninc/libra.git" 69 | branch = "synchro" 70 | 71 | [dependencies.network] 72 | git = "https://github.com/iqlusioninc/libra.git" 73 | branch = "synchro" 74 | 75 | [dependencies.state-synchronizer] 76 | git = "https://github.com/iqlusioninc/libra.git" 77 | branch = "synchro" 78 | 79 | [dependencies.storage-client] 80 | git = "https://github.com/iqlusioninc/libra.git" 81 | branch = "synchro" 82 | 83 | [dependencies.vm-runtime] 84 | git = "https://github.com/iqlusioninc/libra.git" 85 | branch = "synchro" 86 | 87 | [dependencies.vm-validator] 88 | git = "https://github.com/iqlusioninc/libra.git" 89 | branch = "synchro" 90 | -------------------------------------------------------------------------------- /synchro/README.md: -------------------------------------------------------------------------------- 1 | # synchro.rs iqlusion 2 | 3 | [![Build Status][build-image]][build-link] 4 | [![Safety Dance][safety-image]][safety-link] 5 | ![MSRV][msrv-image] 6 | [![Apache 2.0 Licensed][license-image]][license-link] 7 | [![Gitter Chat][gitter-image]][gitter-link] 8 | 9 | Byzantine Fault Tolerant consensus library built on [HotStuff BFT][hotstuff] 10 | used by the [Synchronicity] reproducible build system. 11 | 12 | ## Status 13 | 14 | **synchro** is a work-in-progress and at an early stage of development 15 | and is not ready to be used. Check back later! 16 | 17 | ## Minimum Supported Rust Version 18 | 19 | - Rust **1.39+** 20 | 21 | ## Code of Conduct 22 | 23 | We abide by the [Contributor Covenant][cc-md] and ask that you do as well. 24 | 25 | For more information, please see [CODE_OF_CONDUCT.md][cc-md]. 26 | 27 | ## License 28 | 29 | Copyright © 2019 iqlusion 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | https://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. 42 | 43 | ## Contribution 44 | 45 | Unless you explicitly state otherwise, any contribution intentionally 46 | submitted for inclusion in the work by you shall be licensed as above, 47 | without any additional terms or conditions. 48 | 49 | [//]: # (badges) 50 | 51 | [build-image]: https://github.com/iqlusioninc/synchronicity/workflows/Rust/badge.svg 52 | [build-link]: https://github.com/iqlusioninc/synchronicity/actions 53 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 54 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 55 | [msrv-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg 56 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 57 | [license-link]: https://github.com/iqlusioninc/synchronicity/blob/master/LICENSE 58 | [gitter-image]: https://badges.gitter.im/badge.svg 59 | [gitter-link]: https://gitter.im/iqlusioninc/community 60 | 61 | [//]: # (general links) 62 | 63 | [hotstuff]: https://github.com/libra/libra/tree/master/consensus 64 | [synchronicity]: https://github.com/iqlusioninc/synchronicity 65 | [cc-web]: https://contributor-covenant.org/ 66 | [cc-md]: https://github.com/iqlusioninc/synchronicity/blob/develop/CODE_OF_CONDUCT.md 67 | -------------------------------------------------------------------------------- /synchro/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration types (from `libra-config`) 2 | 3 | pub mod builder; 4 | pub mod key_seed; 5 | pub mod peer_info; 6 | 7 | pub use self::{builder::Builder, key_seed::KeySeed, peer_info::PeerInfo}; 8 | pub use libra_config::{config::*, keys, seed_peers, trusted_peers, utils}; 9 | -------------------------------------------------------------------------------- /synchro/src/config/builder.rs: -------------------------------------------------------------------------------- 1 | //! Libra configuration builder. 2 | //! 3 | //! Parts adapted from upstream Libra's `SwarmConfigBuilder`: 4 | //! 5 | 6 | use super::{ 7 | key_seed::KeySeed, 8 | keys::{ConsensusKeyPair, NetworkKeyPairs}, 9 | peer_info::{self, PeerInfo}, 10 | trusted_peers::{ 11 | self, ConsensusPeersConfig, ConsensusPrivateKey, NetworkPeersConfig, NetworkPrivateKeys, 12 | }, 13 | ConsensusConfig, NetworkConfig, NodeConfig, PersistableConfig, RoleType, 14 | }; 15 | use crate::types::account_address::AccountAddress; 16 | use parity_multiaddr::Multiaddr; 17 | use std::{ 18 | collections::HashMap, 19 | fmt::Display, 20 | path::{Path, PathBuf}, 21 | }; 22 | 23 | /// Default address to listen on 24 | pub const DEFAULT_LISTEN_ADDRESS: &str = "0.0.0.0"; 25 | 26 | /// Default port to listen on 27 | pub const DEFAULT_PORT: u16 = 6180; 28 | 29 | /// Libra configuration builder 30 | pub struct Builder { 31 | /// Seed to use when generating keys (default random) 32 | key_seed: KeySeed, 33 | 34 | /// Output directory 35 | output_dir: PathBuf, 36 | 37 | /// Address to listen on 38 | listen_address: Multiaddr, 39 | 40 | /// Address to advertise to the network 41 | advertised_address: Multiaddr, 42 | 43 | /// Node `RoleType` (either `Validator` or `FullNode`) 44 | role: RoleType, 45 | 46 | /// Is this network permissioned? 47 | is_permissioned: bool, 48 | 49 | /// Seed address to include in `peer_info.toml` 50 | seed_address: Option, 51 | } 52 | 53 | impl Builder { 54 | /// Create a new config builder 55 | pub fn new(key_seed: KeySeed) -> Self { 56 | let listen_address = 57 | parse_multiaddr_from_ipv4(DEFAULT_LISTEN_ADDRESS, DEFAULT_PORT).unwrap(); 58 | 59 | let advertised_address = listen_address.clone(); 60 | 61 | Self { 62 | output_dir: PathBuf::from("."), 63 | listen_address, 64 | advertised_address, 65 | key_seed, 66 | role: RoleType::Validator, 67 | is_permissioned: true, 68 | seed_address: None, 69 | } 70 | } 71 | 72 | /// Set output directory 73 | pub fn with_output_dir(&mut self, output_dir: impl AsRef) -> &mut Self { 74 | self.output_dir = output_dir.as_ref().to_path_buf(); 75 | self 76 | } 77 | 78 | /// Set listen address 79 | pub fn with_listen_address(&mut self, listen_address: impl Display) -> &mut Self { 80 | self.listen_address = parse_multiaddr_from_ipv4(listen_address, DEFAULT_PORT).unwrap(); 81 | self 82 | } 83 | 84 | /// Set advertised address 85 | pub fn with_advertised_address(&mut self, advertised_address: impl Display) -> &mut Self { 86 | self.advertised_address = 87 | parse_multiaddr_from_ipv4(advertised_address, DEFAULT_PORT).unwrap(); 88 | self 89 | } 90 | 91 | /// Configure whether or not the network is permissioned 92 | pub fn with_is_permissioned(&mut self, is_permissioned: bool) -> &mut Self { 93 | // TODO(tarcieri): support permissionless networks 94 | assert!( 95 | !is_permissioned, 96 | "support for `is_permissioned: false` unimplemented" 97 | ); 98 | self 99 | } 100 | 101 | /// Configure a seed IP address for this node to include in `peer_info.toml` 102 | pub fn with_seed_address(&mut self, seed_address: impl Display) -> &mut Self { 103 | self.seed_address = Some(parse_multiaddr_from_ipv4(seed_address, DEFAULT_PORT).unwrap()); 104 | self 105 | } 106 | 107 | /// Generate keys and initial configuration settings 108 | pub fn generate_keys_and_configs( 109 | &self, 110 | domain: &[u8], 111 | version: u32, 112 | ) -> ( 113 | HashMap, 114 | ConsensusPeersConfig, 115 | NetworkPeersConfig, 116 | ) { 117 | let seed = self.key_seed.derive_seed(domain, version); 118 | trusted_peers::ConfigHelpers::gen_validator_nodes(1, Some(seed)) 119 | } 120 | 121 | /// Generate `ConsensusConfig` and write `consensus_keypair.config.toml` 122 | pub fn generate_consensus_config(&self, private_key: ConsensusPrivateKey) -> ConsensusConfig { 123 | let consensus_config = ConsensusConfig::default(); 124 | 125 | let consensus_keypair = ConsensusKeyPair::load(Some(private_key.consensus_private_key)); 126 | let consensus_keypair_file = self 127 | .output_dir 128 | .join(&consensus_config.consensus_keypair_file); 129 | 130 | consensus_keypair.save_config(&consensus_keypair_file); 131 | consensus_config 132 | } 133 | 134 | /// Generate `NetworkConfig` and write `network_keypairs.config.toml` 135 | pub fn generate_network_config( 136 | &self, 137 | peer_id: &str, 138 | private_keys: NetworkPrivateKeys, 139 | ) -> NetworkConfig { 140 | let network_keypairs = NetworkKeyPairs::load( 141 | private_keys.network_signing_private_key, 142 | private_keys.network_identity_private_key, 143 | ); 144 | 145 | let mut network_config = NetworkConfig::default(); 146 | network_config.peer_id = peer_id.to_owned(); 147 | 148 | network_config.role = match self.role { 149 | RoleType::Validator => "validator", 150 | RoleType::FullNode => "full_node", 151 | } 152 | .to_owned(); 153 | 154 | network_config.listen_address = self.listen_address.clone(); 155 | network_config.advertised_address = self.advertised_address.clone(); 156 | network_config.is_permissioned = self.is_permissioned; 157 | 158 | let network_keypairs_file = self.output_dir.join(&network_config.network_keypairs_file); 159 | network_keypairs.save_config(&network_keypairs_file); 160 | network_config 161 | } 162 | 163 | /// Generate `NodeConfig` and write `node.config.toml` 164 | pub fn generate_node_config( 165 | &self, 166 | consensus_config: ConsensusConfig, 167 | network_config: NetworkConfig, 168 | ) -> NodeConfig { 169 | let node_config = NodeConfig { 170 | base: Default::default(), 171 | networks: vec![network_config], 172 | consensus: consensus_config, 173 | metrics: Default::default(), 174 | execution: Default::default(), 175 | admission_control: Default::default(), 176 | debug_interface: Default::default(), 177 | storage: Default::default(), 178 | mempool: Default::default(), 179 | state_sync: Default::default(), 180 | log_collector: Default::default(), 181 | vm_config: Default::default(), 182 | }; 183 | 184 | let node_config_file = self.output_dir.join("node.config.toml"); 185 | node_config.save_config(&node_config_file); 186 | node_config 187 | } 188 | 189 | /// Generate `PeerInfo` and write `peer_info.toml` 190 | pub fn generate_peer_info( 191 | &self, 192 | peer_id: &str, 193 | consensus_peers: ConsensusPeersConfig, 194 | network_peers: NetworkPeersConfig, 195 | ) -> PeerInfo { 196 | let consensus_info = consensus_peers.peers.into_iter().next().unwrap().1; 197 | let network_info = network_peers.peers.into_iter().next().unwrap().1; 198 | let peer_info = PeerInfo::new( 199 | peer_id, 200 | self.seed_address.as_ref(), 201 | consensus_info, 202 | network_info, 203 | ); 204 | 205 | let peer_info_file = self.output_dir.join(peer_info::DEFAULT_FILENAME); 206 | peer_info.save_config(&peer_info_file); 207 | peer_info 208 | } 209 | } 210 | 211 | /// Parse an IP address into a Multiaddr 212 | fn parse_multiaddr_from_ipv4( 213 | ipv4_addr: impl Display, 214 | port: u16, 215 | ) -> Result { 216 | format!("/ip4/{}/tcp/{}", ipv4_addr, port).parse() 217 | } 218 | -------------------------------------------------------------------------------- /synchro/src/config/key_seed.rs: -------------------------------------------------------------------------------- 1 | //! Key Seed: base derivation key for all node cryptographic (private) keys 2 | 3 | use hkd32::mnemonic; 4 | use std::convert::TryInto; 5 | 6 | /// Toplevel path component for personalizing all `synchro`-derived subkeys 7 | pub const TOPLEVEL_DERIVATION_COMPONENT: &[u8] = b"synchro"; 8 | 9 | /// Key Seed 10 | #[derive(Clone)] 11 | pub struct KeySeed(mnemonic::Phrase); 12 | 13 | impl KeySeed { 14 | /// Generate a random KeySeed 15 | pub fn generate() -> Self { 16 | KeySeed(mnemonic::Phrase::random(Default::default())) 17 | } 18 | 19 | /// Get the phrase for this `KeySeed` as a string 20 | pub fn phrase(&self) -> &str { 21 | self.0.phrase() 22 | } 23 | 24 | /// Derive a seed value given a domain and version 25 | pub fn derive_seed(&self, domain: &[u8], version: u32) -> [u8; 32] { 26 | let components = [ 27 | TOPLEVEL_DERIVATION_COMPONENT, 28 | domain, 29 | &version.to_le_bytes(), 30 | ]; 31 | 32 | let mut derivation_path = hkd32::PathBuf::new(); 33 | 34 | for component in &components { 35 | derivation_path.push(hkd32::Component::new(component).unwrap()); 36 | } 37 | 38 | self.0 39 | .clone() 40 | .derive_subkey(&derivation_path) 41 | .as_bytes() 42 | .try_into() 43 | .unwrap() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /synchro/src/config/peer_info.rs: -------------------------------------------------------------------------------- 1 | //! `peer_info.toml` files used to build devnet genesis/configuration 2 | 3 | use libra_config::trusted_peers::{ConsensusPeerInfo, NetworkPeerInfo}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Name of the `PeerInfo` file 7 | pub const DEFAULT_FILENAME: &str = "peer_info.toml"; 8 | 9 | /// Public keys for a particular network peer 10 | #[derive(Clone, Debug, Serialize, Deserialize)] 11 | pub struct PeerInfo { 12 | /// Peer ID 13 | pub id: String, 14 | 15 | /// Optional description for this peer 16 | #[serde(default)] 17 | pub description: String, 18 | 19 | /// Optional web site URL 20 | #[serde(default)] 21 | pub website_url: String, 22 | 23 | /// Optional logo URL 24 | #[serde(default)] 25 | pub logo_url: String, 26 | 27 | /// Seed IP address to include in `peer_info.toml` 28 | pub seed_ip: Option, 29 | 30 | /// Consensus peer information 31 | pub consensus: ConsensusPeerInfo, 32 | 33 | /// Network peer information 34 | pub network: NetworkPeerInfo, 35 | } 36 | 37 | impl PeerInfo { 38 | /// Create new `PeerInfo` from the given peer ID, consensus and network peer info 39 | pub fn new( 40 | peer_id: impl ToString, 41 | seed_ip: Option, 42 | consensus: ConsensusPeerInfo, 43 | network: NetworkPeerInfo, 44 | ) -> Self { 45 | Self { 46 | id: peer_id.to_string(), 47 | description: Default::default(), 48 | website_url: Default::default(), 49 | logo_url: Default::default(), 50 | seed_ip: seed_ip.map(|ip| ip.to_string()), 51 | consensus, 52 | network, 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /synchro/src/crypto.rs: -------------------------------------------------------------------------------- 1 | //! Crypto dependencies 2 | 3 | pub use libra_crypto::{bls12381, ed25519, hash, hkdf, slip0010, test_utils, traits, vrf, x25519}; 4 | -------------------------------------------------------------------------------- /synchro/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | 3 | pub use libra_failure_ext::{Error, Result}; 4 | -------------------------------------------------------------------------------- /synchro/src/launcher.rs: -------------------------------------------------------------------------------- 1 | //! Launcher - starts a node with the given configuration an executor 2 | 3 | use crate::{error::Error, node::Node, transaction::NewVerifier}; 4 | use std::{ 5 | convert::TryInto, 6 | str::FromStr, 7 | sync::{Arc, Mutex}, 8 | time::Instant, 9 | }; 10 | 11 | // Libra types 12 | 13 | use consensus::consensus_provider::{make_consensus_provider, ConsensusProvider}; 14 | use executor::Executor; 15 | use grpc_helpers::ServerHandle; 16 | use grpcio::EnvBuilder; 17 | use libra_config::config::{NodeConfig, RoleType}; 18 | use libra_crypto::ed25519::Ed25519PublicKey; 19 | use libra_mempool::{core_mempool::CoreMempool, mempool_service::MempoolService, MempoolRuntime}; 20 | use libra_types::account_address::AccountAddress as PeerId; 21 | use log::debug; 22 | use network::{ 23 | validator_network::{ 24 | network_builder::{NetworkBuilder, TransportType}, 25 | ConsensusNetworkEvents, 26 | ConsensusNetworkSender, 27 | LibraNetworkProvider, 28 | // when you add a new protocol const, you must add this in either 29 | // .direct_send_protocols or .rpc_protocols vector of network_builder in setup_network() 30 | ADMISSION_CONTROL_RPC_PROTOCOL, 31 | CONSENSUS_DIRECT_SEND_PROTOCOL, 32 | CONSENSUS_RPC_PROTOCOL, 33 | MEMPOOL_DIRECT_SEND_PROTOCOL, 34 | STATE_SYNCHRONIZER_MSG_PROTOCOL, 35 | }, 36 | NetworkPublicKeys, ProtocolId, 37 | }; 38 | use state_synchronizer::StateSynchronizer; 39 | use storage_client::{StorageRead, StorageReadServiceClient, StorageWriteServiceClient}; 40 | use tokio::runtime::Runtime; 41 | use vm_runtime::VMExecutor; 42 | 43 | /// Launcher - launches a Synchro node 44 | pub struct Launcher { 45 | /// Node configuration 46 | node_config: NodeConfig, 47 | 48 | /// Peer ID 49 | peer_id: PeerId, 50 | 51 | /// Node role 52 | role: RoleType, 53 | 54 | /// Transaction verification provider 55 | verify_provider: V, 56 | } 57 | 58 | impl Launcher 59 | where 60 | V: NewVerifier, 61 | { 62 | /// Create a new launcher 63 | pub fn new(node_config: NodeConfig, verify_provider: V) -> Result { 64 | if let Some(net_config) = node_config.networks.get(0) { 65 | let peer_id = PeerId::from_hex_literal(&net_config.peer_id)?; 66 | let role = RoleType::from(&net_config.role); 67 | 68 | Ok(Self { 69 | node_config, 70 | peer_id, 71 | role, 72 | verify_provider, 73 | }) 74 | } else { 75 | // TODO(tarcieri): don't panic! 76 | panic!( 77 | "unexpected number of network configs: {} (expected 1)", 78 | node_config.networks.len() 79 | ); 80 | } 81 | } 82 | 83 | /// Launch the node 84 | pub fn launch(mut self) -> Result, Error> 85 | where 86 | E: VMExecutor + Send + Sync + 'static, 87 | { 88 | let runtime = crate::start_runtime(); 89 | let mut network_provider = self.start_network_provider(&runtime); 90 | 91 | // Note: We need to start network provider before consensus, because the consensus 92 | // initialization is blocked on state synchronizer to sync to the initial root ledger 93 | // info, which in turn cannot make progress before network initialization 94 | // because the NewPeer events which state synchronizer uses to know its 95 | // peers are delivered by network provider. If we were to start network 96 | // provider after consensus, we create a cyclic dependency from 97 | // network provider -> consensus -> state synchronizer -> network provider. This deadlock 98 | // was observed in GitHub Issue #749. A long term fix might be make 99 | // consensus initialization async instead of blocking on state synchronizer. 100 | let mempool = self.start_mempool(network_provider.as_mut()); 101 | 102 | let (consensus_network_sender, consensus_network_events) = 103 | network_provider.add_consensus(vec![ 104 | ProtocolId::from_static(CONSENSUS_RPC_PROTOCOL), 105 | ProtocolId::from_static(CONSENSUS_DIRECT_SEND_PROTOCOL), 106 | ]); 107 | 108 | runtime.executor().spawn(network_provider.start()); 109 | debug!("network started for peer_id: {}", &self.peer_id); 110 | 111 | let executor = self.start_executor(); 112 | let consensus = self.start_consensus_provider( 113 | Arc::clone(&executor), 114 | consensus_network_sender, 115 | consensus_network_events, 116 | )?; 117 | 118 | Ok(Node { 119 | runtime, 120 | consensus, 121 | mempool, 122 | executor, 123 | }) 124 | } 125 | 126 | /// Start the network provider 127 | fn start_network_provider(&mut self, runtime: &Runtime) -> Box { 128 | // NOTE: this is asserted to exist in `Launcher::new` 129 | let network_signing_private = self.node_config.networks[0].network_keypairs.take_network_signing_private().expect( 130 | "failed to move network signing private key out of NodeConfig: key not set or moved already" 131 | ); 132 | 133 | let network_signing_public = Ed25519PublicKey::from(&network_signing_private); 134 | 135 | // NOTE: this is asserted to exist in `Launcher::new` 136 | let network_config = &self.node_config.networks[0]; 137 | 138 | let mut network_builder = NetworkBuilder::new( 139 | runtime.executor(), 140 | self.peer_id, 141 | network_config.listen_address.clone(), 142 | self.role, 143 | ); 144 | 145 | network_builder 146 | .permissioned(network_config.is_permissioned) 147 | .advertised_address(network_config.advertised_address.clone()) 148 | .direct_send_protocols(vec![ 149 | ProtocolId::from_static(CONSENSUS_DIRECT_SEND_PROTOCOL), 150 | ProtocolId::from_static(MEMPOOL_DIRECT_SEND_PROTOCOL), 151 | ProtocolId::from_static(STATE_SYNCHRONIZER_MSG_PROTOCOL), 152 | ]) 153 | .rpc_protocols(vec![ 154 | ProtocolId::from_static(CONSENSUS_RPC_PROTOCOL), 155 | ProtocolId::from_static(ADMISSION_CONTROL_RPC_PROTOCOL), 156 | ]); 157 | 158 | let trusted_peers = network_config 159 | .network_peers 160 | .peers 161 | .iter() 162 | .map(|(peer_id, keys)| { 163 | ( 164 | PeerId::from_str(peer_id).unwrap(), 165 | NetworkPublicKeys { 166 | signing_public_key: keys.network_signing_pubkey.clone(), 167 | identity_public_key: keys.network_identity_pubkey.clone(), 168 | }, 169 | ) 170 | }) 171 | .collect(); 172 | 173 | let seed_peers = network_config 174 | .seed_peers 175 | .seed_peers 176 | .clone() 177 | .into_iter() 178 | .map(|(peer_id, addrs)| (peer_id.try_into().expect("invalid PeerId"), addrs)) 179 | .collect(); 180 | 181 | network_builder 182 | .transport(TransportType::TcpNoise(Some( 183 | network_config 184 | .network_keypairs 185 | .get_network_identity_keypair(), 186 | ))) 187 | .connectivity_check_interval_ms(network_config.connectivity_check_interval_ms) 188 | .seed_peers(seed_peers) 189 | .trusted_peers(trusted_peers) 190 | .signing_keys((network_signing_private, network_signing_public)) 191 | .discovery_interval_ms(network_config.discovery_interval_ms); 192 | 193 | let (listen_addr, network_provider) = network_builder.build(); 194 | debug!("listen addr: {:?}", listen_addr); 195 | 196 | network_provider 197 | } 198 | 199 | /// Start the mempool for this node 200 | fn start_mempool(&self, network_provider: &mut dyn LibraNetworkProvider) -> MempoolRuntime { 201 | let (network_sender, network_events) = network_provider 202 | .add_mempool(vec![ProtocolId::from_static(MEMPOOL_DIRECT_SEND_PROTOCOL)]); 203 | 204 | // Initialize and start mempool. 205 | let instant = Instant::now(); 206 | 207 | let config = &self.node_config; 208 | let mempool = Arc::new(Mutex::new(CoreMempool::new(&config))); 209 | let cq_count = 2; // TODO: customize count based on CPUs? 210 | 211 | // setup grpc server 212 | let env = Arc::new( 213 | EnvBuilder::new() 214 | .name_prefix("grpc-mempool-") 215 | .cq_count(cq_count) 216 | .build(), 217 | ); 218 | 219 | let handle = MempoolService { 220 | core_mempool: Arc::clone(&mempool), 221 | }; 222 | 223 | let service = libra_mempool::proto::mempool::create_mempool(handle); 224 | let grpc_server = grpcio::ServerBuilder::new(env) 225 | .register_service(service) 226 | .bind( 227 | config.mempool.address.clone(), 228 | config.mempool.mempool_service_port, 229 | ) 230 | .build() 231 | .expect("[mempool] unable to create grpc server"); 232 | 233 | // setup shared mempool 234 | let storage_client: Arc = Arc::new(StorageReadServiceClient::new( 235 | Arc::new(EnvBuilder::new().name_prefix("grpc-mem-sto-").build()), 236 | "localhost", 237 | config.storage.port, 238 | )); 239 | 240 | let verifier = Arc::new( 241 | self.verify_provider 242 | .new_verifier(Arc::clone(&storage_client)), 243 | ); 244 | 245 | // TODO(tarcieri): mempool subscribers? 246 | let subscribers = vec![]; 247 | 248 | // TODO(tarcieri): timer? 249 | let timer = None; 250 | 251 | let shared_mempool = libra_mempool::shared_mempool::start_shared_mempool( 252 | config, 253 | mempool, 254 | network_sender, 255 | network_events, 256 | storage_client, 257 | verifier, 258 | subscribers, 259 | timer, 260 | ); 261 | 262 | debug!("mempool started in {} ms", instant.elapsed().as_millis()); 263 | 264 | MempoolRuntime { 265 | grpc_server: ServerHandle::setup(grpc_server), 266 | shared_mempool, 267 | } 268 | } 269 | 270 | /// Start the consensus provider 271 | fn start_consensus_provider( 272 | &mut self, 273 | executor: Arc>, 274 | consensus_network_sender: ConsensusNetworkSender, 275 | consensus_network_events: ConsensusNetworkEvents, 276 | ) -> Result, Error> 277 | where 278 | E: VMExecutor + Send + Sync + 'static, 279 | { 280 | // Initialize and start consensus. 281 | let instant = Instant::now(); 282 | 283 | // TODO(tarcieri): populate these? 284 | let state_sync_network_handles = vec![]; 285 | 286 | let state_synchronizer = StateSynchronizer::bootstrap( 287 | state_sync_network_handles, 288 | Arc::clone(&executor), 289 | &self.node_config, 290 | ); 291 | 292 | let mut consensus_provider = make_consensus_provider( 293 | &mut self.node_config, 294 | consensus_network_sender, 295 | consensus_network_events, 296 | executor, 297 | state_synchronizer.create_client(), 298 | ); 299 | 300 | consensus_provider.start()?; 301 | debug!("consensus started in {} ms", instant.elapsed().as_millis()); 302 | 303 | Ok(consensus_provider) 304 | } 305 | 306 | /// Start the executor for this node 307 | fn start_executor(&self) -> Arc> 308 | where 309 | E: VMExecutor + Send + Sync + 'static, 310 | { 311 | let client_env = Arc::new(EnvBuilder::new().name_prefix("grpc-exe-sto-").build()); 312 | 313 | let storage_read_client = Arc::new(StorageReadServiceClient::new( 314 | Arc::clone(&client_env), 315 | &self.node_config.storage.address, 316 | self.node_config.storage.port, 317 | )); 318 | 319 | let storage_write_client = Arc::new(StorageWriteServiceClient::new( 320 | Arc::clone(&client_env), 321 | &self.node_config.storage.address, 322 | self.node_config.storage.port, 323 | self.node_config.storage.grpc_max_receive_len, 324 | )); 325 | 326 | Arc::new(Executor::new( 327 | Arc::clone(&storage_read_client) as Arc, 328 | storage_write_client, 329 | &self.node_config, 330 | )) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /synchro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Synchro 2 | 3 | // Modules 4 | pub mod config; 5 | pub mod crypto; 6 | pub mod error; 7 | pub mod launcher; 8 | pub mod node; 9 | pub mod transaction; 10 | 11 | // Crate re-exports 12 | pub use grpcio; 13 | 14 | // Libra re-exports 15 | pub use consensus; 16 | pub use executor; 17 | pub use futures; 18 | pub use libra_mempool as mempool; 19 | pub use libra_state_view as state_view; 20 | pub use libra_types as types; 21 | pub use network; 22 | pub use state_synchronizer; 23 | pub use storage_client; 24 | pub use vm_runtime; 25 | pub use vm_validator; 26 | 27 | // Other re-exports 28 | pub use tokio; 29 | 30 | pub use self::{launcher::Launcher, node::Node}; 31 | 32 | /// Helper to initialize a Tokio runtime 33 | pub fn start_runtime() -> tokio::runtime::Runtime { 34 | tokio::runtime::Builder::new() 35 | .name_prefix("synchro-") 36 | .build() 37 | .unwrap_or_else(|e| panic!("couldn't initialize Tokio runtime: {}", e)) 38 | } 39 | -------------------------------------------------------------------------------- /synchro/src/node.rs: -------------------------------------------------------------------------------- 1 | //! Synchronicity node type: owns all state for a running node 2 | 3 | use consensus::consensus_provider::ConsensusProvider; 4 | use executor::Executor; 5 | use libra_mempool::MempoolRuntime; 6 | use std::sync::Arc; 7 | use vm_runtime::VMExecutor; 8 | 9 | /// Synchronicity full node runtime 10 | pub struct Node 11 | where 12 | V: VMExecutor + Send + Sync + 'static, 13 | { 14 | /// Tokio runtime 15 | pub runtime: tokio::runtime::Runtime, 16 | 17 | /// Consensus provider 18 | pub consensus: Box, 19 | 20 | /// Mempool runtime 21 | pub mempool: MempoolRuntime, 22 | 23 | /// Executor 24 | pub executor: Arc>, 25 | } 26 | -------------------------------------------------------------------------------- /synchro/src/transaction.rs: -------------------------------------------------------------------------------- 1 | //! Transaction-related types/traits 2 | 3 | pub use libra_types::{ 4 | transaction::{SignedTransaction, Transaction, TransactionOutput}, 5 | vm_error::VMStatus as Status, 6 | }; 7 | pub use vm_runtime::VMVerifier; 8 | pub use vm_validator::vm_validator::TransactionValidation; 9 | 10 | use std::sync::Arc; 11 | use storage_client::StorageRead; 12 | 13 | /// Initialize a `TransactionValidation`-capable transaction validator 14 | pub trait NewVerifier: Send + Sync { 15 | /// Transaction validator type this trait produces 16 | type Verifier: TransactionValidation + 'static; 17 | 18 | /// Initialize a transaction validator from the given storage reader 19 | fn new_verifier(&self, storage_read_client: Arc) -> Self::Verifier; 20 | } 21 | -------------------------------------------------------------------------------- /tests/acceptance.rs: -------------------------------------------------------------------------------- 1 | //! Acceptance test: runs the application as a subprocess and asserts its 2 | //! output for given argument combinations matches what is expected. 3 | 4 | #![forbid(unsafe_code)] 5 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 6 | 7 | use abscissa_core::testing::prelude::*; 8 | use std::{fs, path::Path}; 9 | use synchro::config::{ConsensusConfig, NetworkConfig, NodeConfig, PeerInfo, PersistableConfig}; 10 | use synchronicity::config::SynchronicityConfig; 11 | use tempfile::tempdir; 12 | 13 | #[test] 14 | fn config_generator() { 15 | let tmp_dir = tempdir().unwrap(); 16 | let dir = tmp_dir.path().canonicalize().unwrap(); 17 | 18 | // Run `synchronicity init {dir}` 19 | run_synchronicity_init(&dir); 20 | 21 | // Ensure `synchronicity.toml` is valid 22 | let config = SynchronicityConfig::load_config(dir.join("synchronicity.toml")); 23 | assert_eq!(&config.node_config, &dir.join("node.config.toml")); 24 | assert_eq!(&config.scratch_dir, &dir.join("scratch")); 25 | 26 | // Make sure the scratch directory exists 27 | assert!(fs::metadata(&config.scratch_dir).unwrap().is_dir()); 28 | 29 | // Make sure the generated Libra `node.config.toml` is valid 30 | let node_config = NodeConfig::load_config(&config.node_config); 31 | assert_eq!( 32 | dir.join(&node_config.consensus.consensus_keypair_file), 33 | dir.join("consensus_keypair.config.toml") 34 | ); 35 | assert_eq!( 36 | dir.join(&node_config.networks[0].network_keypairs_file), 37 | dir.join("network_keypairs.config.toml") 38 | ); 39 | 40 | // Make sure the generated Libra config files load 41 | ConsensusConfig::load_config(dir.join("consensus_keypair.config.toml")); 42 | NetworkConfig::load_config(dir.join("network_keypairs.config.toml")); 43 | PeerInfo::load_config(dir.join("peer_info.toml")); 44 | } 45 | 46 | /// Run `synchronicity init` 47 | fn run_synchronicity_init(output_dir: &Path) { 48 | let mut runner = CmdRunner::default(); 49 | let cmd = runner.arg("init").arg(output_dir).capture_stdout().run(); 50 | 51 | cmd.wait().unwrap().expect_success(); 52 | } 53 | --------------------------------------------------------------------------------