diff --git a/.assets/libra.png b/.assets/libra.png
new file mode 100644
index 0000000000000..a159ebf19494f
Binary files /dev/null and b/.assets/libra.png differ
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000000..733a0f0d3959d
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,39 @@
+version: 2
+jobs:
+ build:
+ docker:
+ - image: circleci/rust:stretch
+ resource_class: xlarge
+ steps:
+ - checkout
+ - run:
+ name: Version Information
+ command: rustc --version; cargo --version; rustup --version
+ - run:
+ name: Install Dependencies
+ command: |
+ sudo sh -c 'echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list'
+ sudo apt-get update
+ sudo apt-get install -y protobuf-compiler/stretch-backports cmake golang curl
+ sudo apt-get clean
+ sudo rm -r /var/lib/apt/lists/*
+ rustup component add clippy rustfmt
+ - run:
+ name: Setup Env
+ command: |
+ echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV
+ echo 'export IMAGE_NAME=myapp' >> $BASH_ENV
+ - run:
+ name: Linting
+ command: |
+ ./scripts/clippy.sh
+ cargo fmt -- --check
+ - run:
+ name: Build All Targets
+ command: RUST_BACKTRACE=1 cargo build -j 16 --all --all-targets
+ - run:
+ name: Run All Unit Tests
+ command: cargo test --all --exclude testsuite
+ - run:
+ name: Run All End to End Tests
+ command: RUST_TEST_THREADS=2 cargo test --package testsuite
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000..a39d1062424a8
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+.git/
+**/.terraform/
+target/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000..8bc545050f9a6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,12 @@
+# Ensure that text files that any contributor introduces to the repository
+# have their line endings normalized to LF
+* text=auto
+
+# All known text filetypes
+*.md text
+*.proto text
+*.rs text
+*.sh text eol=lf
+*.toml text
+*.txt text
+*.yml text
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000000..9cbf62661250f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,43 @@
+---
+name: "\U0001F41B Bug report"
+about: Create a bug report to help improve Libra Core
+title: "[Bug]"
+labels: bug
+assignees: ''
+
+---
+
+# π Bug
+
+
+
+## To reproduce
+
+** Code snippet to reproduce **
+```rust
+# Your code goes here
+# Please make sure it does not require any external dependencies
+```
+
+** Stack trace/error message **
+```
+// Paste the output here
+```
+
+## Expected Behavior
+
+
+
+## System information
+
+**Please complete the following information:**
+-
+-
+-
+
+
+## Additional context
+
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000000..ae9bb986680f9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,32 @@
+---
+name: "\U0001F680 Feature request"
+about: Suggest a new feature in Libra Core
+title: "[Feature Request]"
+labels: enhancement
+assignees: ''
+
+---
+
+# π Feature Request
+
+
+
+## Motivation
+
+**Is your feature request related to a problem? Please describe.**
+
+
+
+## Pitch
+
+**Describe the solution you'd like**
+
+
+**Describe alternatives you've considered**
+
+
+**Are you willing to open a pull request?** (See [CONTRIBUTING](../CONTRIBUTING.md))
+
+## Additional context
+
+
diff --git a/.github/ISSUE_TEMPLATE/questions.md b/.github/ISSUE_TEMPLATE/questions.md
new file mode 100644
index 0000000000000..efb1f94bf73f5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/questions.md
@@ -0,0 +1,10 @@
+---
+name: β Questions/Help
+about: If you have questions, please check Discourse
+---
+
+## β Questions and Help
+
+### Please note that this issue tracker is not a help form and this issue will be closed.
+
+Please contact the development team on [Discourse](https://community.libra.org)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000000..b4824b51dd64d
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,21 @@
+
+
+## Motivation
+
+(Write your motivation for proposed changes here.)
+
+### Have you read the [Contributing Guidelines on pull requests](https://github.com/libra/libra/master/CONTRIBUTING.md#pull-requests)?
+
+(Write your answer here.)
+
+## Test Plan
+
+(Share your test plan here. If you changed code, please provide us with clear instrutions for verifying that your changes work.)
+
+## Related PRs
+
+(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/libra/website, and link to your PR here.)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000..c22208bb5b3e0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+# Rust specific ignores
+/target
+**/*.rs.bk
+# Cargo.lock is needed for deterministic testing and repeatable builds.
+#
+# Having it in the repo slows down development cycle.
+#
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# Ignore generated files in proto folders
+**/proto/*.rs
+!**/proto/mod.rs
+!**/proto/converter.rs
+**/proto/*/*.rs
+!**/proto/*/mod.rs
+!**/proto/*/converter.rs
+
+# IDE
+.idea
+.idea/*
+*.iml
+.vscode
+
+# Ignore wallet mnemonic files used for deterministic key derivation
+*.mnemonic
+
+# Generated Parser File by LALRPOP
+language/compiler/src/parser/syntax.rs
+language/move_ir/
+
+# GDB related
+**/.gdb_history
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000000..659acef601caa
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Code of Conduct
+
+The Libra Core project has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://developers.libra.org/docs/policies/code-of-conduct) so that you can and understand what actions will and will not be tolerated.
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000..fce178f4668e3
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing to Libra
+
+Our goal is to make contributing to the Libra project easy and transparent.
+
+> **Note**: As the Libra Core project is currently an early-stage prototype, it is undergoing rapid development. While we welcome contributions, before making substantial contributions be sure to discuss them in the Discourse forum to ensure that they fit into the project roadmap.
+
+## On Contributing
+
+
+### Libra Core
+
+To contribute to the Libra Core implementation, first start with the proper development copy.
+
+To get the development installation with all the necessary dependencies for linting, testing, and building the documentation, run the following:
+```bash
+git clone https://github.com/libra/libra.git
+cd libra
+./scripts/dev_setup.sh
+cargo build
+cargo test
+```
+
+## Our Development Process
+
+#### Code Style, Hints, and Testing
+
+Refer to our [Coding Guidelines](https://developers.libra.org/docs/coding-guidelines) for detailed guidance about how to contribute to the project.
+
+#### Documentation
+
+Libra's website is also open source (the
+code can be found in this [repository](https://github.com/libra/website/)).
+It is built using [Docusaurus](https://docusaurus.io/):
+
+If you know Markdown, you can already contribute! This lives in the the [website repo](https://github.com/libra/website).
+
+## Pull Requests
+During the initial phase of heavy development, we plan to only audit and review pull requests. As the codebase stabilizes, we will be better able to accept pull requests from the community.
+
+1. Fork the repo and create your branch from `master`.
+2. If you have added code that should be tested, add unit tests.
+3. If you have changed APIs, update the documentation. Make sure the
+ documentation builds.
+4. Ensure the test suite passes.
+5. Make sure your code passes both linters.
+6. If you haven't already, complete the Contributor License Agreement (CLA).
+7. Submit your pull request.
+
+## Contributor License Agreement
+
+For pull request to be accepted by any Libra projects, a CLA must be signed. You will only need to do this once to work on any of Libra's open source projects. Individuals contributing on their own behalf can sign the [Individual CLA](https://github.com/libra/libra/blob/master/contributing/individual-cla.pdf). If you are contributing on behalf of your employer, please ask them to sign the [Corporate CLA](https://github.com/libra/libra/blob/master/contributing/corporate-cla.pdf).
+
+## Issues
+
+Libra uses [GitHub issues](https://github.com/libra/libra/issues) to track bugs. Please include necessary information and instructions to reproduce your issue.
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000000000..33133ba28c779
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,67 @@
+[workspace]
+
+members = [
+ "admission_control/admission_control_service",
+ "admission_control/admission_control_proto",
+ "client",
+ "client/libra_wallet",
+ "common/canonical_serialization",
+ "common/crash_handler",
+ "common/debug_interface",
+ "common/executable_helpers",
+ "common/failure_ext",
+ "common/grpcio-client",
+ "common/jemalloc",
+ "common/logger",
+ "common/metrics",
+ "common/proptest_helpers",
+ "common/proto_conv",
+ "config",
+ "config/config_builder",
+ "config/generate_keypair",
+ "consensus",
+ "crypto/legacy_crypto",
+ "crypto/nextgen_crypto",
+ "crypto/secret_service",
+ "execution/execution_client",
+ "execution/execution_proto",
+ "execution/execution_service",
+ "execution/executor",
+ "language/bytecode_verifier",
+ "language/bytecode_verifier/invalid_mutations",
+ "language/functional_tests",
+ "language/compiler",
+ "language/stdlib/natives",
+ "language/vm",
+ "language/vm/vm_runtime",
+ "language/vm/cost_synthesis",
+ "language/vm/vm_runtime/vm_cache_map",
+ "language/vm/vm_genesis",
+ "language/vm/vm_runtime/vm_runtime_tests",
+ "libra_node",
+ "libra_swarm",
+ "network",
+ "network/memsocket",
+ "network/netcore",
+ "network/noise",
+ "mempool",
+ "storage/accumulator",
+ "storage/libradb",
+ "storage/schemadb",
+ "storage/scratchpad",
+ "storage/sparse_merkle",
+ "storage/storage_client",
+ "storage/state_view",
+ "storage/storage_proto",
+ "storage/storage_service",
+ "testsuite",
+ "testsuite/libra_fuzzer",
+ "types",
+ "vm_validator",
+]
+
+[profile.release]
+debug = true
+
+[profile.bench]
+debug = true
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000..261eeb9e9f8b2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000..28e92e862356e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+[![CircleCI](https://circleci.com/gh/libra/libra.svg?style=shield)](https://circleci.com/gh/libra/libra)
+[![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE.md)
+
+Libra Core implements a decentralized, programmable database which provides a financial infrastructure that can empower billions of people.
+
+## Note to Developers
+* Libra Core is a prototype.
+* The APIs are constantly evolving and designed to demonstrate types of functionality. Expect substantial changes before the release.
+* Weβve launched a testnet that is a live demonstration of an early prototype of the Libra Blockchain software.
+
+## Contributing
+
+Read our [Contrbuting guide](https://developers.libra.org/docs/community/contributing). Find out whatβs coming on our [blog](https://developers.libra.org/blog/2019/06/18/The-Path-Forward).
+
+## Getting Started
+
+### Learn About Libra
+* [Welcome](https://developers.libra.org/docs/welcome-to-libra)
+* [Libra Protocol: Key Concepts](https://developers.libra.org/docs/libra-protocol)
+* [Life of a Transaction](https://developers.libra.org/docs/life-of-a-transaction)
+
+### Try Libra Core
+* [My First Transaction](https://developers.libra.org/docs/my-first-transaction)
+* [Getting Started With Move](https://developers.libra.org/docs/move-overview)
+
+### Technical Papers
+* [The Libra Blockchain](https://developers.libra.org/docs/the-libra-blockchain-paper)
+* [Move: A Language With Programmable Resources](https://developers.libra.org/docs/move-paper)
+* [State Machine Replication in the Libra Blockchain](https://developers.libra.org/docs/state-machine-replication-paper)
+
+### Blog
+* [Libra: The Path Forward](https://developers.libra.org/blog/2019/06/18/the-path-forward/)
+
+### Libra Codebase
+
+* [Libra Core Overview](https://developers.libra.org/docs/libra-core-overview)
+* [Admission Control](https://developers.libra.org/docs/crates/admission-control)
+* [Bytecode Verifier](https://developers.libra.org/docs/crates/bytecode-verifier)
+* [Consensus](https://developers.libra.org/docs/crates/consensus)
+* [Crypto](https://developers.libra.org/docs/crates/crypto)
+* [Execution](https://developers.libra.org/docs/crates/execution)
+* [Mempool](https://developers.libra.org/docs/crates/mempool)
+* [Move IR Compiler](https://developers.libra.org/docs/crates/ir-to-bytecode)
+* [Move Language](https://developers.libra.org/docs/crates/move-language)
+* [Network](https://developers.libra.org/docs/crates/network)
+* [Storage](https://developers.libra.org/docs/crates/storage)
+* [Virtual Machine](https://developers.libra.org/docs/crates/vm)
+
+
+## Community
+
+* Join us on the [Libra Discourse](https://community.libra.org).
+* Get the latest updates to our project by signing up for our [newsletter](https://developers.libra.org/newsletter_form).
+
+## License
+
+Libra Core is licensed as [Apache 2.0](https://github.com/libra/libra/blob/master/LICENSE).
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000000..98febf18056f7
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Security Policies and Procedures
+
+Please see Libra's
+[security policies](https://developers.libra.org/docs/policies/security) and
+procedures for reporting vulnerabilities.
diff --git a/admission_control/README.md b/admission_control/README.md
new file mode 100644
index 0000000000000..2e6e9018454f4
--- /dev/null
+++ b/admission_control/README.md
@@ -0,0 +1,43 @@
+---
+id: admission-control
+title: Admission Control
+custom_edit_url: https://github.com/libra/libra/edit/master/admission_control/README.md
+---
+# Admission Control
+
+Admission Control (AC) is the public API endpoint for Libra and it takes public gRPC requests from clients.
+
+## Overview
+Admission Control (AC) serves two types of requests from clients:
+1. SubmitTransaction - To submit a transaction to the associated validator.
+2. UpdateToLatestLedger - To query storage, e.g., account state, transaction log, proofs, etc.
+
+## Implementation Details
+Admission Control (AC) implements two public APIs:
+1. SubmitTransaction(SubmitTransactionRequest)
+ * Multiple validations will be performed against the request:
+ * The Transaction signature is checked first. If this check fails, AdmissionControlStatus::Rejected is returned to client.
+ * The Transaction is then validated by vm_validator. If this fails, the corresponding VMStatus is returned to the client.
+ * Once the transaction passes all validations, AC queries the sender's account balance and the latest sequence number from storage and sends them to Mempool along with the client request.
+ * If Mempool returns MempoolAddTransactionStatus::Valid, AdmissionControlStatus::Accepted is returned to the client indicating successful submission. Otherwise, corresponding AdmissionControlStatus is returned to the client.
+2. UpdateToLatestLedger(UpdateToLatestLedgerRequest). No extra processing is performed in AC.
+* The request is directly passed to storage for query.
+
+## Folder Structure
+```
+ .
+ βββ README.md
+ βββ admission_control_proto
+ βΒ Β βββ src
+ βΒ Β βββ proto # Protobuf definition files
+ βββ admission_control_service
+ βββ src # gRPC service source files
+ βββ admission_control_node.rs # Wrapper to run AC in a separate thread
+ βββ admission_control_service.rs # gRPC service and main logic
+ βββ main.rs # Main entry to run AC as a binary
+ βββ unit_tests # Tests
+```
+
+## This module interacts with:
+The Mempool component, to submit transactions from clients.
+The Storage component, to query validator storage.
diff --git a/admission_control/admission_control_proto/Cargo.toml b/admission_control/admission_control_proto/Cargo.toml
new file mode 100644
index 0000000000000..455fa4e9248db
--- /dev/null
+++ b/admission_control/admission_control_proto/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "admission_control_proto"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies]
+futures = "0.1.25"
+futures03 = { version = "=0.3.0-alpha.16", package = "futures-preview" }
+grpcio = "0.4.3"
+protobuf = "2.6"
+
+failure = { package = "failure_ext", path = "../../common/failure_ext" }
+logger = { path = "../../common/logger" }
+mempool = { path = "../../mempool" }
+proto_conv = { path = "../../common/proto_conv" }
+types = { path = "../../types" }
+
+[build-dependencies]
+build_helpers = { path = "../../common/build_helpers" }
diff --git a/admission_control/admission_control_proto/build.rs b/admission_control/admission_control_proto/build.rs
new file mode 100644
index 0000000000000..0003f434b5494
--- /dev/null
+++ b/admission_control/admission_control_proto/build.rs
@@ -0,0 +1,19 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! This compiles all the `.proto` files under `src/` directory.
+//!
+//! For example, if there is a file `src/a/b/c.proto`, it will generate `src/a/b/c.rs` and
+//! `src/a/b/c_grpc.rs`.
+
+fn main() {
+ let proto_root = "src";
+ let dependent_root = "../../types/src/proto";
+ let mempool_dependent_root = "../../mempool/src/proto/shared";
+
+ build_helpers::build_helpers::compile_proto(
+ proto_root,
+ vec![dependent_root, mempool_dependent_root],
+ true,
+ );
+}
diff --git a/admission_control/admission_control_proto/src/lib.rs b/admission_control/admission_control_proto/src/lib.rs
new file mode 100644
index 0000000000000..6a0a8b66fe910
--- /dev/null
+++ b/admission_control/admission_control_proto/src/lib.rs
@@ -0,0 +1,110 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+pub mod proto;
+
+use crate::proto::admission_control::AdmissionControlStatus as ProtoAdmissionControlStatus;
+use failure::prelude::*;
+use logger::prelude::*;
+use mempool::MempoolAddTransactionStatus;
+use proto_conv::{FromProto, IntoProto};
+use types::vm_error::VMStatus;
+
+/// AC response status of submit_transaction to clients.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum AdmissionControlStatus {
+ /// Validator accepted the transaction.
+ Accepted = 0,
+ /// The sender is blacklisted.
+ Blacklisted = 1,
+ /// The transaction is rejected, e.g. due to incorrect signature.
+ Rejected = 2,
+}
+
+impl IntoProto for AdmissionControlStatus {
+ type ProtoType = crate::proto::admission_control::AdmissionControlStatus;
+
+ fn into_proto(self) -> Self::ProtoType {
+ match self {
+ AdmissionControlStatus::Accepted => ProtoAdmissionControlStatus::Accepted,
+ AdmissionControlStatus::Blacklisted => ProtoAdmissionControlStatus::Blacklisted,
+ AdmissionControlStatus::Rejected => ProtoAdmissionControlStatus::Rejected,
+ }
+ }
+}
+
+impl FromProto for AdmissionControlStatus {
+ type ProtoType = crate::proto::admission_control::AdmissionControlStatus;
+
+ fn from_proto(object: Self::ProtoType) -> Result {
+ let ret = match object {
+ ProtoAdmissionControlStatus::Accepted => AdmissionControlStatus::Accepted,
+ ProtoAdmissionControlStatus::Blacklisted => AdmissionControlStatus::Blacklisted,
+ ProtoAdmissionControlStatus::Rejected => AdmissionControlStatus::Rejected,
+ };
+ Ok(ret)
+ }
+}
+
+/// Rust structure for SubmitTransactionResponse protobuf definition.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SubmitTransactionResponse {
+ /// AC status returned to client if any, it includes can be either error or accepted status.
+ pub ac_status: Option,
+ /// Mempool error status if any.
+ pub mempool_error: Option,
+ /// VM error status if any.
+ pub vm_error: Option,
+ /// The id of validator associated with this AC.
+ pub validator_id: Vec,
+}
+
+impl IntoProto for SubmitTransactionResponse {
+ type ProtoType = crate::proto::admission_control::SubmitTransactionResponse;
+
+ fn into_proto(self) -> Self::ProtoType {
+ let mut proto = Self::ProtoType::new();
+ if let Some(ac_st) = self.ac_status {
+ proto.set_ac_status(ac_st.into_proto());
+ } else if let Some(mem_err) = self.mempool_error {
+ proto.set_mempool_status(mem_err.into_proto());
+ } else if let Some(vm_st) = self.vm_error {
+ proto.set_vm_status(vm_st.into_proto());
+ } else {
+ error!("No status is available in SubmitTransactionResponse!");
+ }
+ proto.set_validator_id(self.validator_id);
+ proto
+ }
+}
+
+impl FromProto for SubmitTransactionResponse {
+ type ProtoType = crate::proto::admission_control::SubmitTransactionResponse;
+
+ fn from_proto(mut object: Self::ProtoType) -> Result {
+ let ac_status = if object.has_ac_status() {
+ Some(AdmissionControlStatus::from_proto(object.get_ac_status())?)
+ } else {
+ None
+ };
+ let mempool_error = if object.has_mempool_status() {
+ Some(MempoolAddTransactionStatus::from_proto(
+ object.get_mempool_status(),
+ )?)
+ } else {
+ None
+ };
+ let vm_error = if object.has_vm_status() {
+ Some(VMStatus::from_proto(object.take_vm_status())?)
+ } else {
+ None
+ };
+
+ Ok(SubmitTransactionResponse {
+ ac_status,
+ mempool_error,
+ vm_error,
+ validator_id: object.take_validator_id(),
+ })
+ }
+}
diff --git a/admission_control/admission_control_proto/src/proto/admission_control.proto b/admission_control/admission_control_proto/src/proto/admission_control.proto
new file mode 100644
index 0000000000000..c1c4358273618
--- /dev/null
+++ b/admission_control/admission_control_proto/src/proto/admission_control.proto
@@ -0,0 +1,76 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+syntax = "proto3";
+
+package admission_control;
+
+import "get_with_proof.proto";
+import "transaction.proto";
+import "proof.proto";
+import "ledger_info.proto";
+import "vm_errors.proto";
+import "mempool_status.proto";
+
+// -----------------------------------------------------------------------------
+// ---------------- Submit transaction
+// -----------------------------------------------------------------------------
+// The request for transaction submission.
+message SubmitTransactionRequest {
+ // Transaction signed by wallet.
+ types.SignedTransaction signed_txn = 1;
+}
+
+// Additional statuses that are possible from admission control in addition
+// to VM statuses.
+enum AdmissionControlStatus {
+ Accepted = 0;
+ Blacklisted = 1;
+ Rejected = 2;
+}
+
+// The response for transaction submission.
+//
+// How does a client know if their transaction was included?
+// A response from the transaction submission only means that the transaction
+// was successfully added to mempool, but not that it is guaranteed to be
+// included in the chain. Each transaction should include an expiration time in
+// the signed transaction. Let's call this T0. As a client, I submit my
+// transaction to a validator. I now need to poll for the transaction I
+// submitted. I can use the query that takes my account and sequence number. If
+// I receive back that the transaction is completed, I will verify the proofs to
+// ensure that this is the transaction I expected. If I receive a response that
+// my transaction is not yet completed, I must check the latest timestamp in the
+// ledgerInfo that I receive back from the query. If this time is greater than
+// T0, I can be certain that my transaction will never be included. If this
+// time is less than T0, I need to continue polling.
+message SubmitTransactionResponse {
+ // The status of a transaction submission can either be a VM status, or
+ // some other admission control/mempool specific status e.g. Blacklisted.
+ oneof status {
+ types.VMStatus vm_status = 1;
+ AdmissionControlStatus ac_status = 2;
+ mempool.MempoolAddTransactionStatus mempool_status = 3;
+ }
+ // Public key(id) of the validator that processed this transaction
+ bytes validator_id = 4;
+}
+
+// -----------------------------------------------------------------------------
+// ---------------- Service definition
+// -----------------------------------------------------------------------------
+service AdmissionControl {
+ // Public API to submit transaction to a validator.
+ rpc SubmitTransaction(SubmitTransactionRequest)
+ returns (SubmitTransactionResponse) {}
+
+ // This API is used to update the client to the latest ledger version and
+ // optionally also request 1..n other pieces of data. This allows for batch
+ // queries. All queries return proofs that a client should check to validate
+ // the data. Note that if a client only wishes to update to the latest
+ // LedgerInfo and receive the proof of this latest version, they can simply
+ // omit the requested_items (or pass an empty list)
+ rpc UpdateToLatestLedger(
+ types.UpdateToLatestLedgerRequest)
+ returns (types.UpdateToLatestLedgerResponse) {}
+}
diff --git a/admission_control/admission_control_proto/src/proto/mod.rs b/admission_control/admission_control_proto/src/proto/mod.rs
new file mode 100644
index 0000000000000..385a1dafa974d
--- /dev/null
+++ b/admission_control/admission_control_proto/src/proto/mod.rs
@@ -0,0 +1,10 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use mempool::proto::shared::mempool_status;
+use types::proto::*;
+
+/// Auto generated proto src files
+pub mod admission_control;
+/// Auto generated proto src files
+pub mod admission_control_grpc;
diff --git a/admission_control/admission_control_service/Cargo.toml b/admission_control/admission_control_service/Cargo.toml
new file mode 100644
index 0000000000000..be012b1aec287
--- /dev/null
+++ b/admission_control/admission_control_service/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "admission_control_service"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies]
+futures = "0.1.25"
+futures03 = { version = "=0.3.0-alpha.16", package = "futures-preview" }
+grpcio = "0.4.3"
+lazy_static = "1.3.0"
+protobuf = "2.6"
+
+admission_control_proto = { path = "../admission_control_proto" }
+config = { path = "../../config" }
+crypto = { path = "../../crypto/legacy_crypto" }
+debug_interface = { path = "../../common/debug_interface" }
+failure = { package = "failure_ext", path = "../../common/failure_ext" }
+executable_helpers = { path = "../../common/executable_helpers"}
+grpc_helpers = { path = "../../common/grpc_helpers"}
+logger = { path = "../../common/logger" }
+mempool = { path = "../../mempool" }
+metrics = { path = "../../common/metrics" }
+proto_conv = { path = "../../common/proto_conv" }
+storage_client = { path = "../../storage/storage_client" }
+types = { path = "../../types" }
+vm_validator = { path = "../../vm_validator" }
+
+[dev-dependencies]
+storage_service = { path = "../../storage/storage_service" }
+
+[build-dependencies]
+build_helpers = { path = "../../common/build_helpers" }
diff --git a/admission_control/admission_control_service/src/admission_control_node.rs b/admission_control/admission_control_service/src/admission_control_node.rs
new file mode 100644
index 0000000000000..64ab520067e50
--- /dev/null
+++ b/admission_control/admission_control_service/src/admission_control_node.rs
@@ -0,0 +1,133 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::admission_control_service::AdmissionControlService;
+use admission_control_proto::proto::admission_control_grpc;
+use config::config::NodeConfig;
+use debug_interface::{node_debug_service::NodeDebugService, proto::node_debug_interface_grpc};
+use failure::prelude::*;
+use grpc_helpers::spawn_service_thread;
+use grpcio::{ChannelBuilder, EnvBuilder, Environment};
+use logger::prelude::*;
+use mempool::proto::{mempool_client::MempoolClientTrait, mempool_grpc::MempoolClient};
+use std::{sync::Arc, thread};
+use storage_client::{StorageRead, StorageReadServiceClient};
+use vm_validator::vm_validator::VMValidator;
+
+/// Struct to run Admission Control service in a dedicated process. It will be used to spin up
+/// extra AC instances to talk to the same validator.
+pub struct AdmissionControlNode {
+ /// Config used to setup environment for this Admission Control service instance.
+ node_config: NodeConfig,
+}
+
+impl Drop for AdmissionControlNode {
+ fn drop(&mut self) {
+ info!("Drop AdmissionControl node");
+ }
+}
+
+impl AdmissionControlNode {
+ /// Construct a new AdmissionControlNode instance using NodeConfig.
+ pub fn new(node_config: NodeConfig) -> Self {
+ AdmissionControlNode { node_config }
+ }
+
+ /// Setup environment and start a new Admission Control service.
+ pub fn run(&self) -> Result<()> {
+ logger::set_global_log_collector(
+ self.node_config
+ .log_collector
+ .get_log_collector_type()
+ .unwrap(),
+ self.node_config.log_collector.is_async,
+ self.node_config.log_collector.chan_size,
+ );
+ info!("Starting AdmissionControl node",);
+ // Start receiving requests
+ let client_env = Arc::new(EnvBuilder::new().name_prefix("grpc-ac-mem-").build());
+ let mempool_connection_str = format!(
+ "{}:{}",
+ self.node_config.mempool.address, self.node_config.mempool.mempool_service_port
+ );
+ let mempool_channel =
+ ChannelBuilder::new(Arc::clone(&client_env)).connect(&mempool_connection_str);
+
+ self.run_with_clients(
+ Arc::clone(&client_env),
+ Arc::new(MempoolClient::new(mempool_channel)),
+ Some(Arc::new(StorageReadServiceClient::new(
+ Arc::clone(&client_env),
+ &self.node_config.storage.address,
+ self.node_config.storage.port,
+ ))),
+ )
+ }
+
+ /// This method will start a node using the provided clients to external services.
+ /// For now, mempool is a mandatory argument, and storage is Option. If it doesn't exist,
+ /// it'll be generated before starting the node.
+ pub fn run_with_clients(
+ &self,
+ env: Arc,
+ mp_client: Arc,
+ storage_client: Option>,
+ ) -> Result<()> {
+ // create storage client if doesnt exist
+ let storage_client: Arc = match storage_client {
+ Some(c) => c,
+ None => Arc::new(StorageReadServiceClient::new(
+ env,
+ &self.node_config.storage.address,
+ self.node_config.storage.port,
+ )),
+ };
+
+ let vm_validator = Arc::new(VMValidator::new(
+ &self.node_config,
+ Arc::clone(&storage_client),
+ ));
+
+ let handle = AdmissionControlService::new(
+ mp_client,
+ storage_client,
+ vm_validator,
+ self.node_config
+ .admission_control
+ .need_to_check_mempool_before_validation,
+ );
+ let service = admission_control_grpc::create_admission_control(handle);
+
+ let _ac_service_handle = spawn_service_thread(
+ service,
+ self.node_config.admission_control.address.clone(),
+ self.node_config
+ .admission_control
+ .admission_control_service_port,
+ "admission_control",
+ );
+
+ // Start Debug interface
+ let debug_service =
+ node_debug_interface_grpc::create_node_debug_interface(NodeDebugService::new());
+ let _debug_handle = spawn_service_thread(
+ debug_service,
+ self.node_config.admission_control.address.clone(),
+ self.node_config
+ .debug_interface
+ .admission_control_node_debug_port,
+ "debug_service",
+ );
+
+ info!(
+ "Started AdmissionControl node on port {}",
+ self.node_config
+ .admission_control
+ .admission_control_service_port
+ );
+
+ loop {
+ thread::park();
+ }
+ }
+}
diff --git a/admission_control/admission_control_service/src/admission_control_service.rs b/admission_control/admission_control_service/src/admission_control_service.rs
new file mode 100644
index 0000000000000..ac5e9a477e709
--- /dev/null
+++ b/admission_control/admission_control_service/src/admission_control_service.rs
@@ -0,0 +1,230 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! Admission Control (AC) is a module acting as the only public end point. It receives api requests
+//! from external clients (such as wallets) and performs necessary processing before sending them to
+//! next step.
+
+use crate::OP_COUNTERS;
+use admission_control_proto::proto::{
+ admission_control::{
+ AdmissionControlStatus, SubmitTransactionRequest, SubmitTransactionResponse,
+ },
+ admission_control_grpc::AdmissionControl,
+};
+use failure::prelude::*;
+use futures::future::Future;
+use futures03::executor::block_on;
+use grpc_helpers::provide_grpc_response;
+use logger::prelude::*;
+use mempool::proto::{
+ mempool::{AddTransactionWithValidationRequest, HealthCheckRequest},
+ mempool_client::MempoolClientTrait,
+ shared::mempool_status::MempoolAddTransactionStatus::{self, MempoolIsFull},
+};
+use metrics::counters::SVC_COUNTERS;
+use proto_conv::{FromProto, IntoProto};
+use std::sync::Arc;
+use storage_client::StorageRead;
+use types::{
+ proto::get_with_proof::{UpdateToLatestLedgerRequest, UpdateToLatestLedgerResponse},
+ transaction::SignedTransaction,
+};
+use vm_validator::vm_validator::{get_account_state, TransactionValidation};
+
+#[cfg(test)]
+#[path = "unit_tests/admission_control_service_test.rs"]
+mod admission_control_service_test;
+
+/// Struct implementing trait (service handle) AdmissionControlService.
+#[derive(Clone)]
+pub struct AdmissionControlService {
+ /// gRPC client connecting Mempool.
+ mempool_client: Arc,
+ /// gRPC client to send read requests to Storage.
+ storage_read_client: Arc,
+ /// VM validator instance to validate transactions sent from wallets.
+ vm_validator: Arc,
+ /// Flag indicating whether we need to check mempool before validation, drop txn if check
+ /// fails.
+ need_to_check_mempool_before_validation: bool,
+}
+
+impl AdmissionControlService
+where
+ M: MempoolClientTrait,
+ V: TransactionValidation,
+{
+ /// Constructs a new AdmissionControlService instance.
+ pub fn new(
+ mempool_client: Arc,
+ storage_read_client: Arc,
+ vm_validator: Arc,
+ need_to_check_mempool_before_validation: bool,
+ ) -> Self {
+ AdmissionControlService {
+ mempool_client,
+ storage_read_client,
+ vm_validator,
+ need_to_check_mempool_before_validation,
+ }
+ }
+
+ /// Validate transaction signature, then via VM, and add it to Mempool if it passes VM check.
+ pub(crate) fn submit_transaction_inner(
+ &self,
+ req: SubmitTransactionRequest,
+ ) -> Result {
+ // Drop requests first if mempool is full (validator is lagging behind) so not to consume
+ // unnecessary resources.
+ if !self.can_send_txn_to_mempool()? {
+ debug!("Mempool is full");
+ OP_COUNTERS.inc_by("submit_txn.rejected.mempool_full", 1);
+ let mut response = SubmitTransactionResponse::new();
+ response.set_mempool_status(MempoolIsFull);
+ return Ok(response);
+ }
+
+ let signed_txn_proto = req.get_signed_txn();
+
+ let signed_txn = match SignedTransaction::from_proto(signed_txn_proto.clone()) {
+ Ok(t) => t,
+ Err(e) => {
+ security_log(SecurityEvent::InvalidTransactionAC)
+ .error(&e)
+ .data(&signed_txn_proto)
+ .log();
+ let mut response = SubmitTransactionResponse::new();
+ response.set_ac_status(AdmissionControlStatus::Rejected);
+ OP_COUNTERS.inc_by("submit_txn.rejected.invalid_txn", 1);
+ return Ok(response);
+ }
+ };
+
+ let gas_cost = signed_txn.max_gas_amount();
+ let validation_status = self
+ .vm_validator
+ .validate_transaction(signed_txn.clone())
+ .wait()
+ .map_err(|e| {
+ security_log(SecurityEvent::InvalidTransactionAC)
+ .error(&e)
+ .data(&signed_txn)
+ .log();
+ e
+ })?;
+ if let Some(validation_status) = validation_status {
+ let mut response = SubmitTransactionResponse::new();
+ OP_COUNTERS.inc_by("submit_txn.vm_validation.failure", 1);
+ debug!(
+ "txn failed in vm validation, status: {:?}, txn: {:?}",
+ validation_status, signed_txn
+ );
+ response.set_vm_status(validation_status.into_proto());
+ return Ok(response);
+ }
+ let sender = signed_txn.sender();
+ let account_state = block_on(get_account_state(self.storage_read_client.clone(), sender));
+ let mut add_transaction_request = AddTransactionWithValidationRequest::new();
+ add_transaction_request.signed_txn = req.signed_txn.clone();
+ add_transaction_request.set_max_gas_cost(gas_cost);
+
+ if let Ok((sequence_number, balance)) = account_state {
+ add_transaction_request.set_account_balance(balance);
+ add_transaction_request.set_latest_sequence_number(sequence_number);
+ }
+
+ self.add_txn_to_mempool(add_transaction_request)
+ }
+
+ fn can_send_txn_to_mempool(&self) -> Result {
+ if self.need_to_check_mempool_before_validation {
+ let req = HealthCheckRequest::new();
+ let is_mempool_healthy = self.mempool_client.health_check(&req)?.get_is_healthy();
+ return Ok(is_mempool_healthy);
+ }
+ Ok(true)
+ }
+
+ /// Add signed transaction to mempool once it passes vm check
+ fn add_txn_to_mempool(
+ &self,
+ add_transaction_request: AddTransactionWithValidationRequest,
+ ) -> Result {
+ let mempool_result = self
+ .mempool_client
+ .add_transaction_with_validation(&add_transaction_request)?;
+
+ debug!("[GRPC] Done with transaction submission request");
+ let mut response = SubmitTransactionResponse::new();
+ if mempool_result.get_status() == MempoolAddTransactionStatus::Valid {
+ OP_COUNTERS.inc_by("submit_txn.txn_accepted", 1);
+ response.set_ac_status(AdmissionControlStatus::Accepted);
+ } else {
+ debug!(
+ "txn failed in mempool, status: {:?}, txn: {:?}",
+ mempool_result,
+ add_transaction_request.get_signed_txn()
+ );
+ OP_COUNTERS.inc_by("submit_txn.mempool.failure", 1);
+ response.set_mempool_status(mempool_result.get_status());
+ }
+ Ok(response)
+ }
+
+ /// Pass the UpdateToLatestLedgerRequest to Storage for read query.
+ fn update_to_latest_ledger_inner(
+ &self,
+ req: UpdateToLatestLedgerRequest,
+ ) -> Result {
+ let rust_req = types::get_with_proof::UpdateToLatestLedgerRequest::from_proto(req)?;
+ let (response_items, ledger_info_with_sigs, validator_change_events) = self
+ .storage_read_client
+ .update_to_latest_ledger(rust_req.client_known_version, rust_req.requested_items)?;
+ let rust_resp = types::get_with_proof::UpdateToLatestLedgerResponse::new(
+ response_items,
+ ledger_info_with_sigs,
+ validator_change_events,
+ );
+ Ok(rust_resp.into_proto())
+ }
+}
+
+impl AdmissionControl for AdmissionControlService
+where
+ M: MempoolClientTrait,
+ V: TransactionValidation,
+{
+ /// Submit a transaction to the validator this AC instance connecting to.
+ /// The specific transaction will be first validated by VM and then passed
+ /// to Mempool for further processing.
+ fn submit_transaction(
+ &mut self,
+ ctx: ::grpcio::RpcContext<'_>,
+ req: SubmitTransactionRequest,
+ sink: ::grpcio::UnarySink,
+ ) {
+ debug!("[GRPC] AdmissionControl::submit_transaction");
+ let _timer = SVC_COUNTERS.req(&ctx);
+ let resp = self.submit_transaction_inner(req);
+ provide_grpc_response(resp, ctx, sink);
+ }
+
+ /// This API is used to update the client to the latest ledger version and optionally also
+ /// request 1..n other pieces of data. This allows for batch queries. All queries return
+ /// proofs that a client should check to validate the data.
+ /// Note that if a client only wishes to update to the latest LedgerInfo and receive the proof
+ /// of this latest version, they can simply omit the requested_items (or pass an empty list).
+ /// AC will not directly process this request but pass it to Storage instead.
+ fn update_to_latest_ledger(
+ &mut self,
+ ctx: grpcio::RpcContext<'_>,
+ req: types::proto::get_with_proof::UpdateToLatestLedgerRequest,
+ sink: grpcio::UnarySink,
+ ) {
+ debug!("[GRPC] AdmissionControl::update_to_latest_ledger");
+ let _timer = SVC_COUNTERS.req(&ctx);
+ let resp = self.update_to_latest_ledger_inner(req);
+ provide_grpc_response(resp, ctx, sink);
+ }
+}
diff --git a/admission_control/admission_control_service/src/lib.rs b/admission_control/admission_control_service/src/lib.rs
new file mode 100644
index 0000000000000..eb7c46f72393d
--- /dev/null
+++ b/admission_control/admission_control_service/src/lib.rs
@@ -0,0 +1,25 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+#![deny(missing_docs)]
+
+//! Admission Control
+//!
+//! Admission Control (AC) is the public API end point taking public gRPC requests from clients.
+//! AC serves two types of request from clients:
+//! 1. SubmitTransaction, to submit transaction to associated validator.
+//! 2. UpdateToLatestLedger, to query storage, e.g. account state, transaction log, and proofs.
+
+/// Wrapper to run AC in a separate process.
+pub mod admission_control_node;
+/// AC gRPC service.
+pub mod admission_control_service;
+use lazy_static::lazy_static;
+use metrics::OpMetrics;
+
+lazy_static! {
+ static ref OP_COUNTERS: OpMetrics = OpMetrics::new_and_registered("admission_control");
+}
+
+#[cfg(test)]
+mod unit_tests;
diff --git a/admission_control/admission_control_service/src/main.rs b/admission_control/admission_control_service/src/main.rs
new file mode 100644
index 0000000000000..e7c3c478f947c
--- /dev/null
+++ b/admission_control/admission_control_service/src/main.rs
@@ -0,0 +1,22 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use admission_control_service::admission_control_node;
+use executable_helpers::helpers::{
+ setup_executable, ARG_CONFIG_PATH, ARG_DISABLE_LOGGING, ARG_PEER_ID,
+};
+
+/// Run a Admission Control service in its own process.
+/// It will also setup global logger and initialize config.
+fn main() {
+ let (config, _logger, _args) = setup_executable(
+ "Libra AdmissionControl node".to_string(),
+ vec![ARG_PEER_ID, ARG_CONFIG_PATH, ARG_DISABLE_LOGGING],
+ );
+
+ let admission_control_node = admission_control_node::AdmissionControlNode::new(config);
+
+ admission_control_node
+ .run()
+ .expect("Unable to run AdmissionControl node");
+}
diff --git a/admission_control/admission_control_service/src/unit_tests/admission_control_service_test.rs b/admission_control/admission_control_service/src/unit_tests/admission_control_service_test.rs
new file mode 100644
index 0000000000000..037752de737de
--- /dev/null
+++ b/admission_control/admission_control_service/src/unit_tests/admission_control_service_test.rs
@@ -0,0 +1,280 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{
+ admission_control_service::{
+ AdmissionControlService, SubmitTransactionRequest,
+ SubmitTransactionResponse as ProtoSubmitTransactionResponse,
+ },
+ unit_tests::LocalMockMempool,
+};
+use admission_control_proto::{AdmissionControlStatus, SubmitTransactionResponse};
+use crypto::{
+ hash::CryptoHash,
+ signing::{generate_keypair, sign_message},
+};
+use mempool::MempoolAddTransactionStatus;
+use proto_conv::FromProto;
+use protobuf::{Message, UnknownFields};
+use std::sync::Arc;
+use storage_service::mocks::mock_storage_client::MockStorageReadClient;
+use types::{
+ account_address::{AccountAddress, ADDRESS_LENGTH},
+ test_helpers::transaction_test_helpers::get_test_signed_txn,
+ transaction::RawTransactionBytes,
+ vm_error::{ExecutionStatus, VMStatus, VMValidationStatus},
+};
+use vm_validator::mocks::mock_vm_validator::MockVMValidator;
+
+fn create_ac_service_for_ut() -> AdmissionControlService {
+ AdmissionControlService::new(
+ Arc::new(LocalMockMempool::new()),
+ Arc::new(MockStorageReadClient),
+ Arc::new(MockVMValidator),
+ false,
+ )
+}
+
+fn assert_status(response: ProtoSubmitTransactionResponse, status: VMStatus) {
+ let rust_resp = SubmitTransactionResponse::from_proto(response).unwrap();
+ if rust_resp.ac_status.is_some() {
+ assert_eq!(
+ rust_resp.ac_status.unwrap(),
+ AdmissionControlStatus::Accepted
+ );
+ } else {
+ let decoded_response = rust_resp.vm_error.unwrap();
+ assert_eq!(decoded_response, status)
+ }
+}
+
+#[test]
+fn test_submit_txn_inner_vm() {
+ let ac_service = create_ac_service_for_ut();
+ // create request
+ let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
+ let sender = AccountAddress::new([0; ADDRESS_LENGTH]);
+ let keypair = generate_keypair();
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::SendingAccountDoesNotExist(
+ "TEST".to_string(),
+ )),
+ );
+ let sender = AccountAddress::new([1; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::InvalidSignature),
+ );
+ let sender = AccountAddress::new([2; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::InsufficientBalanceForTransactionFee),
+ );
+ let sender = AccountAddress::new([3; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::SequenceNumberTooNew),
+ );
+ let sender = AccountAddress::new([4; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::SequenceNumberTooOld),
+ );
+ let sender = AccountAddress::new([5; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::TransactionExpired),
+ );
+ let sender = AccountAddress::new([6; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(
+ response,
+ VMStatus::Validation(VMValidationStatus::InvalidAuthKey),
+ );
+ let sender = AccountAddress::new([8; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
+ assert_status(response, VMStatus::Execution(ExecutionStatus::Executed));
+ let sender = AccountAddress::new([8; ADDRESS_LENGTH]);
+ let test_key = generate_keypair();
+ req.set_signed_txn(get_test_signed_txn(
+ sender,
+ 0,
+ keypair.0.clone(),
+ test_key.1,
+ None,
+ ));
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.ac_status.unwrap(),
+ AdmissionControlStatus::Rejected,
+ );
+}
+
+#[test]
+fn test_reject_unknown_fields() {
+ let ac_service = create_ac_service_for_ut();
+ let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
+ let keypair = generate_keypair();
+ let sender = AccountAddress::random();
+ let mut signed_txn = get_test_signed_txn(sender, 0, keypair.0.clone(), keypair.1, None);
+ let mut raw_txn = protobuf::parse_from_bytes::<::types::proto::transaction::RawTransaction>(
+ signed_txn.raw_txn_bytes.as_ref(),
+ )
+ .unwrap();
+ let mut unknown_fields = UnknownFields::new();
+ unknown_fields.add_fixed32(1, 2);
+ raw_txn.unknown_fields = unknown_fields;
+
+ let bytes = raw_txn.write_to_bytes().unwrap();
+ let hash = RawTransactionBytes(&bytes).hash();
+ let signature = sign_message(hash, &keypair.0).unwrap();
+
+ signed_txn.set_raw_txn_bytes(bytes);
+ signed_txn.set_sender_signature(signature.to_compact().to_vec());
+ req.set_signed_txn(signed_txn);
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.ac_status.unwrap(),
+ AdmissionControlStatus::Rejected
+ );
+}
+
+#[test]
+fn test_submit_txn_inner_mempool() {
+ let ac_service = create_ac_service_for_ut();
+ let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
+ let keypair = generate_keypair();
+ let insufficient_balance_add = AccountAddress::new([100; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ insufficient_balance_add,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.mempool_error.unwrap(),
+ MempoolAddTransactionStatus::InsufficientBalance,
+ );
+ let invalid_seq_add = AccountAddress::new([101; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ invalid_seq_add,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.mempool_error.unwrap(),
+ MempoolAddTransactionStatus::InvalidSeqNumber,
+ );
+ let sys_error_add = AccountAddress::new([102; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ sys_error_add,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.mempool_error.unwrap(),
+ MempoolAddTransactionStatus::InvalidUpdate,
+ );
+ let accepted_add = AccountAddress::new([103; ADDRESS_LENGTH]);
+ req.set_signed_txn(get_test_signed_txn(
+ accepted_add,
+ 0,
+ keypair.0.clone(),
+ keypair.1,
+ None,
+ ));
+ let response = SubmitTransactionResponse::from_proto(
+ ac_service.submit_transaction_inner(req.clone()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(
+ response.ac_status.unwrap(),
+ AdmissionControlStatus::Accepted,
+ );
+}
diff --git a/admission_control/admission_control_service/src/unit_tests/mod.rs b/admission_control/admission_control_service/src/unit_tests/mod.rs
new file mode 100644
index 0000000000000..b6c6541b4ad00
--- /dev/null
+++ b/admission_control/admission_control_service/src/unit_tests/mod.rs
@@ -0,0 +1,65 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use mempool::proto::{
+ mempool::{
+ AddTransactionWithValidationRequest, AddTransactionWithValidationResponse,
+ HealthCheckRequest, HealthCheckResponse,
+ },
+ mempool_client::MempoolClientTrait,
+ shared::mempool_status::MempoolAddTransactionStatus,
+};
+use proto_conv::FromProto;
+use std::time::SystemTime;
+use types::{account_address::ADDRESS_LENGTH, transaction::SignedTransaction};
+
+// Define a local mempool to use for unit tests here, ignore methods not used by the test
+#[derive(Clone)]
+pub struct LocalMockMempool {
+ created_time: SystemTime,
+}
+
+impl LocalMockMempool {
+ pub fn new() -> Self {
+ Self {
+ created_time: SystemTime::now(),
+ }
+ }
+}
+
+impl MempoolClientTrait for LocalMockMempool {
+ fn add_transaction_with_validation(
+ &self,
+ req: &AddTransactionWithValidationRequest,
+ ) -> ::grpcio::Result {
+ let mut resp = AddTransactionWithValidationResponse::new();
+ let insufficient_balance_add = [100_u8; ADDRESS_LENGTH];
+ let invalid_seq_add = [101_u8; ADDRESS_LENGTH];
+ let sys_error_add = [102_u8; ADDRESS_LENGTH];
+ let accepted_add = [103_u8; ADDRESS_LENGTH];
+ let mempool_full = [104_u8; ADDRESS_LENGTH];
+ let signed_txn = SignedTransaction::from_proto(req.get_signed_txn().clone()).unwrap();
+ let sender = signed_txn.sender();
+ if sender.as_ref() == insufficient_balance_add {
+ resp.set_status(MempoolAddTransactionStatus::InsufficientBalance);
+ } else if sender.as_ref() == invalid_seq_add {
+ resp.set_status(MempoolAddTransactionStatus::InvalidSeqNumber);
+ } else if sender.as_ref() == sys_error_add {
+ resp.set_status(MempoolAddTransactionStatus::InvalidUpdate);
+ } else if sender.as_ref() == accepted_add {
+ resp.set_status(MempoolAddTransactionStatus::Valid);
+ } else if sender.as_ref() == mempool_full {
+ resp.set_status(MempoolAddTransactionStatus::MempoolIsFull);
+ }
+ Ok(resp)
+ }
+ fn health_check(&self, _req: &HealthCheckRequest) -> ::grpcio::Result {
+ let mut ret = HealthCheckResponse::new();
+ let duration_ms = SystemTime::now()
+ .duration_since(self.created_time)
+ .unwrap()
+ .as_millis();
+ ret.set_is_healthy(duration_ms > 500 || duration_ms < 300);
+ Ok(ret)
+ }
+}
diff --git a/client/Cargo.toml b/client/Cargo.toml
new file mode 100644
index 0000000000000..ce8267c78948b
--- /dev/null
+++ b/client/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "client"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies]
+bincode = "1.1.1"
+chrono = "0.4.6"
+futures = "0.1.23"
+grpcio = "0.4.3"
+hex = "0.3.2"
+hyper = "0.12"
+itertools = "0.8.0"
+proptest = "0.9.2"
+protobuf = "2.6"
+rand = "0.6.5"
+rustyline = "4.1.0"
+tokio = "0.1.16"
+rust_decimal = "1.0.1"
+num-traits = "0.2"
+serde = { version = "1.0.89", features = ["derive"] }
+structopt = "0.2.15"
+
+admission_control_proto = { version = "0.1.0", path = "../admission_control/admission_control_proto" }
+config = { path = "../config" }
+crash_handler = { path = "../common/crash_handler" }
+crypto = { path = "../crypto/legacy_crypto" }
+failure = { package = "failure_ext", path = "../common/failure_ext" }
+libc = "0.2.48"
+libra_wallet = { path = "./libra_wallet" }
+logger = { path = "../common/logger" }
+metrics = { path = "../common/metrics" }
+proto_conv = { path = "../common/proto_conv" }
+types = { path = "../types" }
+vm_genesis = { path = "../language/vm/vm_genesis" }
+
+[dev-dependencies]
+tempfile = "3.0.6"
diff --git a/client/libra_wallet/Cargo.toml b/client/libra_wallet/Cargo.toml
new file mode 100644
index 0000000000000..93ba790647dac
--- /dev/null
+++ b/client/libra_wallet/Cargo.toml
@@ -0,0 +1,43 @@
+[package]
+name = "libra_wallet"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies.ed25519-dalek]
+version = "1.0.0-pre.1"
+
+[dependencies.types]
+path = "../../types"
+
+[dependencies.libra_crypto]
+path = "../../crypto/legacy_crypto"
+package = "crypto"
+
+[dependencies.proto_conv]
+path = "../../common/proto_conv"
+
+[dependencies.failure]
+path = "../../common/failure_ext"
+package = "failure_ext"
+
+[dependencies]
+rust-crypto = "0.2"
+log = "0.4"
+simple_logger = "0.5"
+rand = "0.6.5"
+rand_chacha = "0.1.1"
+rand_core = "0.4.0"
+hex = "0.3"
+byteorder = "1.2.6"
+serde = "1"
+serde_derive = "1"
+serde_json = "1.0.31"
+tiny-keccak = "1.4.2"
+protobuf = "2.6"
+sha3 = "0.8.2"
+
+[dev-dependencies]
+tempfile = "3.0.6"
diff --git a/client/libra_wallet/README.md b/client/libra_wallet/README.md
new file mode 100644
index 0000000000000..aaabecfe1475b
--- /dev/null
+++ b/client/libra_wallet/README.md
@@ -0,0 +1,15 @@
+# Libra Wallet
+
+Libra Wallet is a pure-rust implementation of hierarchical key derivation for SecretKey material in Libra.
+
+# Overview
+
+`libra_wallet` is a library providing hierarchical key derivation for SecretKey material in Libra. The following crate is largely inspired by [`rust-wallet`](https://github.com/rust-bitcoin/rust-wallet) with minor modifications to the key derivation function. Note that Libra makes use of the ed25519 Edwards Curve Digital Signature Algorithm (EdDSA) over the Edwards Cruve cruve25519. Therefore, BIP32-like PublicKey derivation is not possible without falling back to a traditional non-deterministic Schnorr signature algorithm. For this reason, we modified the key derivation function to a simpler alternative.
+
+The `internal_macros.rs` is taken from [`rust-bitcoin`](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/src/internal_macros.rs) and `mnemonic.rs` is a slightly modified version of the file with the same name from [`rust-wallet`](https://github.com/rust-bitcoin/rust-wallet/blob/master/wallet/src/mnemonic.rs), while `error.rs`, `key_factor.rs` and `wallet_library.rs` are modified to present a minimalist wallet library for the Libra Client. Note that `mnemonic.rs` from `rust-wallet` adheres to the [`BIP39`](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) spec.
+
+# Implementation Details
+
+`key_factory.rs` implements the key derivation functions. The `KeyFactory` struct holds the Master Secret Material used to derive the Child Key(s). The constructor of a particular `KeyFactory` accepts a `[u8; 64]` `Seed` and computes both the `Master` Secret Material as well as the `ChainCode` from the HMAC-512 of the `Seed`. Finally, the `KeyFactory` allows to derive a child PrivateKey at a particular `ChildNumber` from the Master and ChainCode, as well as the `ChildNumber`'s u64 member.
+
+`wallet_library.rs` is a thin wrapper around `KeyFactory` which enables to keep track of Libra `AccountAddresses` and the information required to restore the current wallet from a `Mnemonic` backup. The `WalletLibrary` struct includes constructors that allow to generate a new `WalletLibrary` from OS randomness or generate a `WalletLibrary` from an instance of `Mnemonic`. `WalletLibrary` also allows to generate new addresses in-order or out-of-order via the `fn new_address` and `fn new_address_at_child_number`. Finally, `WalletLibrary` is capable of signing a Libra `RawTransaction` withe PrivateKey associated to the `AccountAddress` submitted. Note that in the future, Libra will support rotating authentication keys and therefore, `WalletLibrary` will need to understand more general inputs when mapping `AuthenticationKeys` to `PrivateKeys`
diff --git a/client/libra_wallet/src/error.rs b/client/libra_wallet/src/error.rs
new file mode 100644
index 0000000000000..e71f01f281498
--- /dev/null
+++ b/client/libra_wallet/src/error.rs
@@ -0,0 +1,83 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use failure;
+use libra_crypto::hkdf::HkdfError;
+use std::{convert, error::Error, fmt, io};
+
+/// We define our own Result type in order to not have to import the libra/common/failture_ext
+pub type Result = ::std::result::Result;
+
+/// Libra Wallet Error is a convenience enum for generating arbitarary WalletErrors. Curently, only
+/// the LibraWalletGeneric error is being used, but there are plans to add more specific errors as
+/// LibraWallet matures
+pub enum WalletError {
+ /// generic error message
+ LibraWalletGeneric(String),
+}
+
+impl Error for WalletError {
+ fn description(&self) -> &str {
+ match *self {
+ WalletError::LibraWalletGeneric(ref s) => s,
+ }
+ }
+
+ fn cause(&self) -> Option<&Error> {
+ match *self {
+ WalletError::LibraWalletGeneric(_) => None,
+ }
+ }
+}
+
+impl fmt::Display for WalletError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ WalletError::LibraWalletGeneric(ref s) => write!(f, "LibraWalletGeneric: {}", s),
+ }
+ }
+}
+
+impl fmt::Debug for WalletError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ (self as &fmt::Display).fmt(f)
+ }
+}
+
+impl convert::From for io::Error {
+ fn from(_err: WalletError) -> io::Error {
+ match _err {
+ WalletError::LibraWalletGeneric(s) => io::Error::new(io::ErrorKind::Other, s),
+ }
+ }
+}
+
+impl convert::From for WalletError {
+ fn from(err: io::Error) -> WalletError {
+ WalletError::LibraWalletGeneric(err.description().to_string())
+ }
+}
+
+impl convert::From for WalletError {
+ fn from(err: failure::prelude::Error) -> WalletError {
+ WalletError::LibraWalletGeneric(format!("{}", err))
+ }
+}
+
+impl convert::From for WalletError {
+ fn from(err: protobuf::error::ProtobufError) -> WalletError {
+ WalletError::LibraWalletGeneric(err.description().to_string())
+ }
+}
+
+impl convert::From for WalletError {
+ fn from(err: ed25519_dalek::SignatureError) -> WalletError {
+ WalletError::LibraWalletGeneric(format!("{}", err))
+ }
+}
+
+impl convert::From for WalletError {
+ fn from(err: HkdfError) -> WalletError {
+ WalletError::LibraWalletGeneric(format!("{}", err))
+ }
+}
diff --git a/client/libra_wallet/src/internal_macros.rs b/client/libra_wallet/src/internal_macros.rs
new file mode 100644
index 0000000000000..594eb53e8f82d
--- /dev/null
+++ b/client/libra_wallet/src/internal_macros.rs
@@ -0,0 +1,245 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! The following macros are slightly modified from rust-bitcoin. The original file may be found
+//! here:
+//!
+//! https://github.com/rust-bitcoin/rust-bitcoin/blob/master/src/internal_macros.rs
+
+macro_rules! impl_array_newtype {
+ ($thing:ident, $ty:ty, $len:expr) => {
+ impl $thing {
+ #[inline]
+ /// Converts the object to a raw pointer
+ pub fn as_ptr(&self) -> *const $ty {
+ let &$thing(ref dat) = self;
+ dat.as_ptr()
+ }
+
+ #[inline]
+ /// Converts the object to a mutable raw pointer
+ pub fn as_mut_ptr(&mut self) -> *mut $ty {
+ let &mut $thing(ref mut dat) = self;
+ dat.as_mut_ptr()
+ }
+
+ #[inline]
+ /// Returns the length of the object as an array
+ pub fn len(&self) -> usize {
+ $len
+ }
+
+ #[inline]
+ /// Returns whether the object, as an array, is empty. Always false.
+ pub fn is_empty(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ /// Returns the underlying bytes.
+ pub fn as_bytes(&self) -> &[$ty; $len] {
+ &self.0
+ }
+
+ #[inline]
+ /// Returns the underlying bytes.
+ pub fn to_bytes(&self) -> [$ty; $len] {
+ self.0.clone()
+ }
+
+ #[inline]
+ /// Returns the underlying bytes.
+ pub fn into_bytes(self) -> [$ty; $len] {
+ self.0
+ }
+ }
+
+ impl<'a> From<&'a [$ty]> for $thing {
+ fn from(data: &'a [$ty]) -> $thing {
+ assert_eq!(data.len(), $len);
+ let mut ret = [0; $len];
+ ret.copy_from_slice(&data[..]);
+ $thing(ret)
+ }
+ }
+
+ impl ::std::ops::Index for $thing {
+ type Output = $ty;
+
+ #[inline]
+ fn index(&self, index: usize) -> &$ty {
+ let &$thing(ref dat) = self;
+ &dat[index]
+ }
+ }
+
+ impl_index_newtype!($thing, $ty);
+
+ impl PartialEq for $thing {
+ #[inline]
+ fn eq(&self, other: &$thing) -> bool {
+ &self[..] == &other[..]
+ }
+ }
+
+ impl Eq for $thing {}
+
+ impl PartialOrd for $thing {
+ #[inline]
+ fn partial_cmp(&self, other: &$thing) -> Option<::std::cmp::Ordering> {
+ Some(self.cmp(&other))
+ }
+ }
+
+ impl Ord for $thing {
+ #[inline]
+ fn cmp(&self, other: &$thing) -> ::std::cmp::Ordering {
+ // manually implement comparison to get little-endian ordering
+ // (we need this for our numeric types; non-numeric ones shouldn't
+ // be ordered anyway except to put them in BTrees or whatever, and
+ // they don't care how we order as long as we're consistent).
+ for i in 0..$len {
+ if self[$len - 1 - i] < other[$len - 1 - i] {
+ return ::std::cmp::Ordering::Less;
+ }
+ if self[$len - 1 - i] > other[$len - 1 - i] {
+ return ::std::cmp::Ordering::Greater;
+ }
+ }
+ ::std::cmp::Ordering::Equal
+ }
+ }
+
+ #[cfg_attr(feature = "clippy", allow(expl_impl_clone_on_copy))] // we don't define the `struct`, we have to explicitly impl
+ impl Clone for $thing {
+ #[inline]
+ fn clone(&self) -> $thing {
+ $thing::from(&self[..])
+ }
+ }
+
+ impl Copy for $thing {}
+
+ impl ::std::hash::Hash for $thing {
+ #[inline]
+ fn hash(&self, state: &mut H)
+ where
+ H: ::std::hash::Hasher,
+ {
+ (&self[..]).hash(state);
+ }
+
+ fn hash_slice(data: &[$thing], state: &mut H)
+ where
+ H: ::std::hash::Hasher,
+ {
+ for d in data.iter() {
+ (&d[..]).hash(state);
+ }
+ }
+ }
+ };
+}
+
+macro_rules! impl_array_newtype_encodable {
+ ($thing:ident, $ty:ty, $len:expr) => {
+ #[cfg(feature = "serde")]
+ impl<'de> $crate::serde::Deserialize<'de> for $thing {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: $crate::serde::Deserializer<'de>,
+ {
+ use $crate::std::fmt::{self, Formatter};
+
+ struct Visitor;
+ impl<'de> $crate::serde::de::Visitor<'de> for Visitor {
+ type Value = $thing;
+
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str("a fixed size array")
+ }
+
+ #[inline]
+ fn visit_seq(self, mut seq: A) -> Result
+ where
+ A: $crate::serde::de::SeqAccess<'de>,
+ {
+ let mut ret: [$ty; $len] = [0; $len];
+ for item in ret.iter_mut() {
+ *item = match seq.next_element()? {
+ Some(c) => c,
+ None => {
+ return Err($crate::serde::de::Error::custom("end of stream"))
+ }
+ };
+ }
+ Ok($thing(ret))
+ }
+ }
+
+ deserializer.deserialize_seq(Visitor)
+ }
+ }
+
+ #[cfg(feature = "serde")]
+ impl $crate::serde::Serialize for $thing {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: $crate::serde::Serializer,
+ {
+ let &$thing(ref dat) = self;
+ (&dat[..]).serialize(serializer)
+ }
+ }
+ };
+}
+
+macro_rules! impl_array_newtype_show {
+ ($thing:ident) => {
+ impl ::std::fmt::Debug for $thing {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, concat!(stringify!($thing), "({:?})"), &self[..])
+ }
+ }
+ };
+}
+
+macro_rules! impl_index_newtype {
+ ($thing:ident, $ty:ty) => {
+ impl ::std::ops::Index<::std::ops::Range> for $thing {
+ type Output = [$ty];
+
+ #[inline]
+ fn index(&self, index: ::std::ops::Range) -> &[$ty] {
+ &self.0[index]
+ }
+ }
+
+ impl ::std::ops::Index<::std::ops::RangeTo> for $thing {
+ type Output = [$ty];
+
+ #[inline]
+ fn index(&self, index: ::std::ops::RangeTo) -> &[$ty] {
+ &self.0[index]
+ }
+ }
+
+ impl ::std::ops::Index<::std::ops::RangeFrom> for $thing {
+ type Output = [$ty];
+
+ #[inline]
+ fn index(&self, index: ::std::ops::RangeFrom) -> &[$ty] {
+ &self.0[index]
+ }
+ }
+
+ impl ::std::ops::Index<::std::ops::RangeFull> for $thing {
+ type Output = [$ty];
+
+ #[inline]
+ fn index(&self, _: ::std::ops::RangeFull) -> &[$ty] {
+ &self.0[..]
+ }
+ }
+ };
+}
diff --git a/client/libra_wallet/src/io_utils.rs b/client/libra_wallet/src/io_utils.rs
new file mode 100644
index 0000000000000..d53679cad01c6
--- /dev/null
+++ b/client/libra_wallet/src/io_utils.rs
@@ -0,0 +1,47 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! A module to generate, store and load known users accounts.
+//! The concept of known users can be helpful for testing to provide reproducable results.
+
+use crate::*;
+use failure::prelude::*;
+use std::{
+ fs::File,
+ io::{BufRead, BufReader, Write},
+ path::Path,
+};
+
+/// Delimiter used to ser/deserialize account data.
+pub const DELIMITER: &str = ";";
+
+/// Recover wallet from the path specified.
+pub fn recover>(path: &P) -> Result {
+ let input = File::open(path)?;
+ let mut buffered = BufReader::new(input);
+
+ let mut line = String::new();
+ let _ = buffered.read_line(&mut line)?;
+ let parts: Vec<&str> = line.split(DELIMITER).collect();
+ ensure!(parts.len() == 2, format!("Invalid entry '{}'", line));
+
+ let mnemonic = Mnemonic::from(&parts[0].to_string()[..])?;
+ let mut wallet = WalletLibrary::new_from_mnemonic(mnemonic);
+ wallet.generate_addresses(parts[1].trim().to_string().parse::()?)?;
+
+ Ok(wallet)
+}
+
+/// Write wallet seed to file.
+pub fn write_recovery>(wallet: &WalletLibrary, path: &P) -> Result<()> {
+ let mut output = File::create(path)?;
+ writeln!(
+ output,
+ "{}{}{}",
+ wallet.mnemonic().to_string(),
+ DELIMITER,
+ wallet.key_leaf()
+ )?;
+
+ Ok(())
+}
diff --git a/client/libra_wallet/src/key_factory.rs b/client/libra_wallet/src/key_factory.rs
new file mode 100644
index 0000000000000..4978e9e63a5a4
--- /dev/null
+++ b/client/libra_wallet/src/key_factory.rs
@@ -0,0 +1,240 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! The following is a minimalist version of a hierarchical key derivation library for the
+//! LibraWallet.
+//!
+//! Note that the Libra Blockchain makes use of ed25519 Edwards Digital Signature Algorithm
+//! (EdDSA) and therefore, BIP32 Public Key derivation is not available without falling back to
+//! a non-deterministic Schnorr signature scheme. As LibraWallet is meant to be a minimalist
+//! reference implementation of a simple wallet, the following does not deviate from the
+//! ed25519 spec. In a future iteration of this wallet, we will also provide an implementation
+//! of a Schnorr variant over curve25519 and demonstrate our proposal for BIP32-like public key
+//! derivation.
+//!
+//! Note further that the Key Derivation Function (KDF) chosen in the derivation of Child
+//! Private Keys adheres to [HKDF RFC 5869](https://tools.ietf.org/html/rfc5869).
+
+use byteorder::{ByteOrder, LittleEndian};
+use crypto::{hmac::Hmac as CryptoHmac, pbkdf2::pbkdf2, sha3::Sha3};
+use ed25519_dalek;
+use libra_crypto::{hash::HashValue, hkdf::Hkdf};
+use serde::{Deserialize, Serialize};
+use sha3::Sha3_256;
+use std::{convert::TryFrom, ops::AddAssign};
+use tiny_keccak::Keccak;
+use types::account_address::AccountAddress;
+
+use crate::{error::Result, mnemonic::Mnemonic};
+
+/// Master is a set of raw bytes that are used for child key derivation
+pub struct Master([u8; 32]);
+impl_array_newtype!(Master, u8, 32);
+impl_array_newtype_show!(Master);
+impl_array_newtype_encodable!(Master, u8, 32);
+
+/// A child number for a derived key, used to derive a certain private key from the Master
+#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
+pub struct ChildNumber(pub(crate) u64);
+
+impl ChildNumber {
+ /// Constructor from u64
+ pub fn new(child_number: u64) -> Self {
+ Self(child_number)
+ }
+
+ /// Bump the ChildNumber
+ pub fn increment(&mut self) {
+ self.add_assign(Self(1));
+ }
+}
+
+impl std::ops::AddAssign for ChildNumber {
+ fn add_assign(&mut self, other: Self) {
+ *self = Self(self.0 + other.0)
+ }
+}
+
+impl std::convert::AsRef for ChildNumber {
+ fn as_ref(&self) -> &u64 {
+ &self.0
+ }
+}
+
+impl std::convert::AsMut for ChildNumber {
+ fn as_mut(&mut self) -> &mut u64 {
+ &mut self.0
+ }
+}
+
+/// Derived private key.
+pub struct ExtendedPrivKey {
+ /// Child number of the key used to derive from Parent.
+ _child_number: ChildNumber,
+ /// Private key.
+ private_key: ed25519_dalek::SecretKey,
+}
+
+impl ExtendedPrivKey {
+ /// Constructor for creating an ExtendedPrivKey from a ed25519 PrivateKey. Note that the
+ /// ChildNumber are not used in this iteration of LibraWallet, but in order to
+ /// enable more general Hierarchical KeyDerivation schemes, we include it for completeness.
+ pub fn new(_child_number: ChildNumber, private_key: ed25519_dalek::SecretKey) -> Self {
+ Self {
+ _child_number,
+ private_key,
+ }
+ }
+
+ /// Returns the PublicKey associated to a particular ExtendedPrivKey
+ pub fn get_public(&self) -> ed25519_dalek::PublicKey {
+ (&self.private_key).into()
+ }
+
+ /// Computes the sha3 hash of the PublicKey and attempts to construct a Libra AccountAddress
+ /// from the raw bytes of the pubkey hash
+ pub fn get_address(&self) -> Result {
+ let public_key = self.get_public();
+ let mut keccak = Keccak::new_sha3_256();
+ let mut hash = [0u8; 32];
+ keccak.update(&public_key.to_bytes());
+ keccak.finalize(&mut hash);
+ let addr = AccountAddress::try_from(&hash[..])?;
+ Ok(addr)
+ }
+
+ /// Libra specific sign function that is capable of signing an arbitrary HashValue
+ /// NOTE: In Libra, we do not sign the raw bytes of a transaction, instead we sign the raw
+ /// bytes of the sha3 hash of the raw bytes of a transaction. It is important to note that the
+ /// raw bytes of the sha3 hash will be hashed again as part of the ed25519 signature algorithm.
+ /// In other words: In Libra, the message used for signature and verification is the sha3 hash
+ /// of the transaction. This sha3 hash is then hashed again using SHA512 to arrive at the
+ /// deterministic nonce for the EdDSA.
+ pub fn sign(&self, msg: HashValue) -> ed25519_dalek::Signature {
+ let public_key: ed25519_dalek::PublicKey = (&self.private_key).into();
+ let expanded_secret_key: ed25519_dalek::ExpandedSecretKey =
+ ed25519_dalek::ExpandedSecretKey::from(&self.private_key);
+ expanded_secret_key.sign(msg.as_ref(), &public_key)
+ }
+}
+
+/// Wrapper struct from which we derive child keys
+pub struct KeyFactory {
+ master: Master,
+}
+
+impl KeyFactory {
+ const MNEMONIC_SALT_PREFIX: &'static [u8] = b"LIBRA WALLET: mnemonic salt prefix$";
+ const MASTER_KEY_SALT: &'static [u8] = b"LIBRA WALLET: master key salt$";
+ const INFO_PREFIX: &'static [u8] = b"LIBRA WALLET: derived key$";
+ /// Instantiate a new KeyFactor from a Seed, where the [u8; 64] raw bytes of the Seed are used
+ /// to derive both the Master
+ pub fn new(seed: &Seed) -> Result {
+ let hkdf_extract = Hkdf::::extract(Some(KeyFactory::MASTER_KEY_SALT), &seed.0)?;
+
+ Ok(Self {
+ master: Master::from(&hkdf_extract[..32]),
+ })
+ }
+
+ /// Getter for the Master
+ pub fn master(&self) -> &[u8] {
+ &self.master.0[..]
+ }
+
+ /// Derive a particular PrivateKey at a certain ChildNumber
+ ///
+ /// Note that the function below adheres to [HKDF RFC 5869](https://tools.ietf.org/html/rfc5869).
+ pub fn private_child(&self, child: ChildNumber) -> Result {
+ // application info in the HKDF context is defined as Libra derived key$child_number.
+ let mut le_n = [0u8; 8];
+ LittleEndian::write_u64(&mut le_n, child.0);
+ let mut info = KeyFactory::INFO_PREFIX.to_vec();
+ info.extend_from_slice(&le_n);
+
+ let hkdf_expand = Hkdf::::expand(&self.master(), Some(&info), 32)?;
+ let sk = ed25519_dalek::SecretKey::from_bytes(&hkdf_expand)?;
+
+ Ok(ExtendedPrivKey::new(child, sk))
+ }
+}
+
+/// Seed is the ouput of a one-way function, which accepts a Mnemonic as input
+pub struct Seed([u8; 32]);
+
+impl Seed {
+ /// Get the underlying Seed internal data
+ pub fn data(&self) -> Vec {
+ self.0.to_vec()
+ }
+}
+
+impl Seed {
+ /// This constructor implements the one-way function that allows to generate a Seed from a
+ /// particular Mnemonic and salt. WalletLibrary implements a fixed salt, but a user could
+ /// choose a user-defined salt instead of the hardcoded one.
+ pub fn new(mnemonic: &Mnemonic, salt: &str) -> Seed {
+ let mut mac = CryptoHmac::new(Sha3::sha3_256(), mnemonic.to_string().as_bytes());
+ let mut output = [0u8; 32];
+
+ let mut msalt = KeyFactory::MNEMONIC_SALT_PREFIX.to_vec();
+ msalt.extend_from_slice(salt.as_bytes());
+
+ pbkdf2(&mut mac, &msalt, 2048, &mut output);
+ Seed(output)
+ }
+}
+
+#[test]
+fn assert_default_child_number() {
+ assert_eq!(ChildNumber::default(), ChildNumber(0));
+}
+
+#[test]
+fn test_key_derivation() {
+ let data = hex::decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap();
+ let mnemonic = Mnemonic::from("legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will").unwrap();
+ assert_eq!(
+ mnemonic.to_string(),
+ Mnemonic::mnemonic(&data).unwrap().to_string()
+ );
+ let seed = Seed::new(&mnemonic, "LIBRA");
+
+ let key_factory = KeyFactory::new(&seed).unwrap();
+ assert_eq!(
+ "16274c9618ed59177ca948529c1884ba65c57984d562ec2b4e5aa1ee3e3903be",
+ hex::encode(&key_factory.master())
+ );
+
+ // Check child_0 key derivation.
+ let child_private_0 = key_factory.private_child(ChildNumber(0)).unwrap();
+ assert_eq!(
+ "358a375f36d74c30b7f3299b62d712b307725938f8cc931100fbd10a434fc8b9",
+ hex::encode(&child_private_0.private_key.to_bytes()[..])
+ );
+
+ // Check determinism, regenerate child_0.
+ let child_private_0_again = key_factory.private_child(ChildNumber(0)).unwrap();
+ assert_eq!(
+ hex::encode(&child_private_0.private_key.to_bytes()[..]),
+ hex::encode(&child_private_0_again.private_key.to_bytes()[..])
+ );
+
+ // Check child_1 key derivation.
+ let child_private_1 = key_factory.private_child(ChildNumber(1)).unwrap();
+ assert_eq!(
+ "a325fe7d27b1b49f191cc03525951fec41b6ffa2d4b3007bb1d9dd353b7e56a6",
+ hex::encode(&child_private_1.private_key.to_bytes()[..])
+ );
+
+ let mut child_1_again = ChildNumber(0);
+ child_1_again.increment();
+ assert_eq!(ChildNumber(1), child_1_again);
+
+ // Check determinism, regenerate child_1, but by incrementing ChildNumber(0).
+ let child_private_1_from_increment = key_factory.private_child(child_1_again).unwrap();
+ assert_eq!(
+ "a325fe7d27b1b49f191cc03525951fec41b6ffa2d4b3007bb1d9dd353b7e56a6",
+ hex::encode(&child_private_1_from_increment.private_key.to_bytes()[..])
+ );
+}
diff --git a/client/libra_wallet/src/lib.rs b/client/libra_wallet/src/lib.rs
new file mode 100644
index 0000000000000..70372e83f5990
--- /dev/null
+++ b/client/libra_wallet/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+/// Error crate
+pub mod error;
+
+/// Internal macros
+#[macro_use]
+pub mod internal_macros;
+
+/// Utils for read/write
+pub mod io_utils;
+
+/// Utils for key derivation
+pub mod key_factory;
+
+/// Utils for mnemonic seed
+pub mod mnemonic;
+
+/// Utils for wallet library
+pub mod wallet_library;
+
+/// Default imports
+pub use crate::{mnemonic::Mnemonic, wallet_library::WalletLibrary};
diff --git a/client/libra_wallet/src/mnemonic.rs b/client/libra_wallet/src/mnemonic.rs
new file mode 100644
index 0000000000000..e3f18d9bc1c42
--- /dev/null
+++ b/client/libra_wallet/src/mnemonic.rs
@@ -0,0 +1,340 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! The following is a slightly modified version of the file with the same name in the
+//! rust-wallet crate. The original file may be found here:
+//!
+//! https://github.com/rust-bitcoin/rust-wallet/blob/master/wallet/src/mnemonic.rs
+use crate::error::*;
+use crypto::{digest::Digest, sha2::Sha256};
+#[cfg(test)]
+use rand::rngs::EntropyRng;
+#[cfg(test)]
+use rand_core::RngCore;
+use std::{
+ fs::{self, File},
+ io::Write,
+ path::Path,
+};
+
+#[cfg(test)]
+use tempfile::NamedTempFile;
+
+/// Mnemonic seed for deterministic key derivation
+pub struct Mnemonic(Vec<&'static str>);
+
+impl ToString for Mnemonic {
+ fn to_string(&self) -> String {
+ self.0.as_slice().join(" ")
+ }
+}
+
+impl Mnemonic {
+ /// Generate mnemonic from string
+ pub fn from(s: &str) -> Result {
+ let words: Vec<_> = s.split(' ').collect();
+ if words.len() < 6 || words.len() % 6 != 0 {
+ return Err(WalletError::LibraWalletGeneric(
+ "Mnemonic must have a word count divisible with 6".to_string(),
+ ));
+ }
+ let mut mnemonic = Vec::new();
+ for word in &words {
+ if let Ok(idx) = WORDS.binary_search(word) {
+ mnemonic.push(WORDS[idx]);
+ } else {
+ return Err(WalletError::LibraWalletGeneric(
+ "Mneminic contains an unknown word".to_string(),
+ ));
+ }
+ }
+ Ok(Mnemonic(mnemonic))
+ }
+
+ /// Generate mnemonic from byte-array
+ pub fn mnemonic(data: &[u8]) -> Result {
+ if data.len() % 4 != 0 {
+ return Err(WalletError::LibraWalletGeneric(
+ "Data for mnemonic should have a length divisible by 4".to_string(),
+ ));
+ }
+ let mut check = [0u8; 32];
+
+ let mut sha2 = Sha256::new();
+ sha2.input(data);
+ sha2.result(&mut check);
+
+ let mut bits = vec![false; data.len() * 8 + data.len() / 4];
+ for i in 0..data.len() {
+ for j in 0..8 {
+ bits[i * 8 + j] = (data[i] & (1 << (7 - j))) > 0;
+ }
+ }
+ for i in 0..data.len() / 4 {
+ bits[8 * data.len() + i] = (check[i / 8] & (1 << (7 - (i % 8)))) > 0;
+ }
+ let mlen = data.len() * 3 / 4;
+ let mut memo = Vec::new();
+ for i in 0..mlen {
+ let mut idx = 0;
+ for j in 0..11 {
+ if bits[i * 11 + j] {
+ idx += 1 << (10 - j);
+ }
+ }
+ memo.push(WORDS[idx]);
+ }
+ Ok(Mnemonic(memo))
+ }
+
+ /// Write mnemonic to output_file_path
+ pub fn write(&self, output_file_path: &Path) -> Result<()> {
+ if output_file_path.exists() && !output_file_path.is_file() {
+ return Err(WalletError::LibraWalletGeneric(format!(
+ "Output file {:?} for mnemonic backup is reserved",
+ output_file_path.to_str(),
+ )));
+ }
+ let mut file = File::create(output_file_path)?;
+ file.write_all(&self.to_string().as_bytes())?;
+ Ok(())
+ }
+
+ /// Read mnemonic from input_file_path
+ pub fn read(input_file_path: &Path) -> Result {
+ if input_file_path.exists() && input_file_path.is_file() {
+ let mnemonic_string: String = fs::read_to_string(input_file_path)?;
+ return Self::from(&mnemonic_string[..]);
+ }
+ Err(WalletError::LibraWalletGeneric(
+ "Input file for mnemonic backup does not exist".to_string(),
+ ))
+ }
+}
+
+#[test]
+fn test_roundtrip_mnemonic() {
+ let mut rng = EntropyRng::new();
+ let mut buf = [0u8; 32];
+ rng.fill_bytes(&mut buf[..]);
+ let file = NamedTempFile::new().unwrap();
+ let path = file.into_temp_path();
+ let mnemonic = Mnemonic::mnemonic(&buf[..]).unwrap();
+ mnemonic.write(&path).unwrap();
+ let other_mnemonic = Mnemonic::read(&path).unwrap();
+ assert_eq!(mnemonic.to_string(), other_mnemonic.to_string());
+}
+
+const WORDS: [&str; 2048] = [
+ "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd",
+ "abuse", "access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire",
+ "across", "act", "action", "actor", "actress", "actual", "adapt", "add", "addict", "address",
+ "adjust", "admit", "adult", "advance", "advice", "aerobic", "affair", "afford", "afraid",
+ "again", "age", "agent", "agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album",
+ "alcohol", "alert", "alien", "all", "alley", "allow", "almost", "alone", "alpha", "already",
+ "also", "alter", "always", "amateur", "amazing", "among", "amount", "amused", "analyst",
+ "anchor", "ancient", "anger", "angle", "angry", "animal", "ankle", "announce", "annual",
+ "another", "answer", "antenna", "antique", "anxiety", "any", "apart", "apology", "appear",
+ "apple", "approve", "april", "arch", "arctic", "area", "arena", "argue", "arm", "armed",
+ "armor", "army", "around", "arrange", "arrest", "arrive", "arrow", "art", "artefact", "artist",
+ "artwork", "ask", "aspect", "assault", "asset", "assist", "assume", "asthma", "athlete",
+ "atom", "attack", "attend", "attitude", "attract", "auction", "audit", "august", "aunt",
+ "author", "auto", "autumn", "average", "avocado", "avoid", "awake", "aware", "away", "awesome",
+ "awful", "awkward", "axis", "baby", "bachelor", "bacon", "badge", "bag", "balance", "balcony",
+ "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain", "barrel", "base", "basic",
+ "basket", "battle", "beach", "bean", "beauty", "because", "become", "beef", "before", "begin",
+ "behave", "behind", "believe", "below", "belt", "bench", "benefit", "best", "betray", "better",
+ "between", "beyond", "bicycle", "bid", "bike", "bind", "biology", "bird", "birth", "bitter",
+ "black", "blade", "blame", "blanket", "blast", "bleak", "bless", "blind", "blood", "blossom",
+ "blouse", "blue", "blur", "blush", "board", "boat", "body", "boil", "bomb", "bone", "bonus",
+ "book", "boost", "border", "boring", "borrow", "boss", "bottom", "bounce", "box", "boy",
+ "bracket", "brain", "brand", "brass", "brave", "bread", "breeze", "brick", "bridge", "brief",
+ "bright", "bring", "brisk", "broccoli", "broken", "bronze", "broom", "brother", "brown",
+ "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb", "bulk", "bullet", "bundle",
+ "bunker", "burden", "burger", "burst", "bus", "business", "busy", "butter", "buyer", "buzz",
+ "cabbage", "cabin", "cable", "cactus", "cage", "cake", "call", "calm", "camera", "camp", "can",
+ "canal", "cancel", "candy", "cannon", "canoe", "canvas", "canyon", "capable", "capital",
+ "captain", "car", "carbon", "card", "cargo", "carpet", "carry", "cart", "case", "cash",
+ "casino", "castle", "casual", "cat", "catalog", "catch", "category", "cattle", "caught",
+ "cause", "caution", "cave", "ceiling", "celery", "cement", "census", "century", "cereal",
+ "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", "chase",
+ "chat", "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child",
+ "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon",
+ "circle", "citizen", "city", "civil", "claim", "clap", "clarify", "claw", "clay", "clean",
+ "clerk", "clever", "click", "client", "cliff", "climb", "clinic", "clip", "clock", "clog",
+ "close", "cloth", "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast",
+ "coconut", "code", "coffee", "coil", "coin", "collect", "color", "column", "combine", "come",
+ "comfort", "comic", "common", "company", "concert", "conduct", "confirm", "congress",
+ "connect", "consider", "control", "convince", "cook", "cool", "copper", "copy", "coral",
+ "core", "corn", "correct", "cost", "cotton", "couch", "country", "couple", "course", "cousin",
+ "cover", "coyote", "crack", "cradle", "craft", "cram", "crane", "crash", "crater", "crawl",
+ "crazy", "cream", "credit", "creek", "crew", "cricket", "crime", "crisp", "critic", "crop",
+ "cross", "crouch", "crowd", "crucial", "cruel", "cruise", "crumble", "crunch", "crush", "cry",
+ "crystal", "cube", "culture", "cup", "cupboard", "curious", "current", "curtain", "curve",
+ "cushion", "custom", "cute", "cycle", "dad", "damage", "damp", "dance", "danger", "daring",
+ "dash", "daughter", "dawn", "day", "deal", "debate", "debris", "decade", "december", "decide",
+ "decline", "decorate", "decrease", "deer", "defense", "define", "defy", "degree", "delay",
+ "deliver", "demand", "demise", "denial", "dentist", "deny", "depart", "depend", "deposit",
+ "depth", "deputy", "derive", "describe", "desert", "design", "desk", "despair", "destroy",
+ "detail", "detect", "develop", "device", "devote", "diagram", "dial", "diamond", "diary",
+ "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", "dinosaur",
+ "direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display",
+ "distance", "divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll",
+ "dolphin", "domain", "donate", "donkey", "donor", "door", "dose", "double", "dove", "draft",
+ "dragon", "drama", "drastic", "draw", "dream", "dress", "drift", "drill", "drink", "drip",
+ "drive", "drop", "drum", "dry", "duck", "dumb", "dune", "during", "dust", "dutch", "duty",
+ "dwarf", "dynamic", "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy",
+ "echo", "ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", "either",
+ "elbow", "elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else",
+ "embark", "embody", "embrace", "emerge", "emotion", "employ", "empower", "empty", "enable",
+ "enact", "end", "endless", "endorse", "enemy", "energy", "enforce", "engage", "engine",
+ "enhance", "enjoy", "enlist", "enough", "enrich", "enroll", "ensure", "enter", "entire",
+ "entry", "envelope", "episode", "equal", "equip", "era", "erase", "erode", "erosion", "error",
+ "erupt", "escape", "essay", "essence", "estate", "eternal", "ethics", "evidence", "evil",
+ "evoke", "evolve", "exact", "example", "excess", "exchange", "excite", "exclude", "excuse",
+ "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit", "exotic", "expand",
+ "expect", "expire", "explain", "expose", "express", "extend", "extra", "eye", "eyebrow",
+ "fabric", "face", "faculty", "fade", "faint", "faith", "fall", "false", "fame", "family",
+ "famous", "fan", "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father", "fatigue",
+ "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female",
+ "fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", "figure", "file",
+ "film", "filter", "final", "find", "fine", "finger", "finish", "fire", "firm", "first",
+ "fiscal", "fish", "fit", "fitness", "fix", "flag", "flame", "flash", "flat", "flavor", "flee",
+ "flight", "flip", "float", "flock", "floor", "flower", "fluid", "flush", "fly", "foam",
+ "focus", "fog", "foil", "fold", "follow", "food", "foot", "force", "forest", "forget", "fork",
+ "fortune", "forum", "forward", "fossil", "foster", "found", "fox", "fragile", "frame",
+ "frequent", "fresh", "friend", "fringe", "frog", "front", "frost", "frown", "frozen", "fruit",
+ "fuel", "fun", "funny", "furnace", "fury", "future", "gadget", "gain", "galaxy", "gallery",
+ "game", "gap", "garage", "garbage", "garden", "garlic", "garment", "gas", "gasp", "gate",
+ "gather", "gauge", "gaze", "general", "genius", "genre", "gentle", "genuine", "gesture",
+ "ghost", "giant", "gift", "giggle", "ginger", "giraffe", "girl", "give", "glad", "glance",
+ "glare", "glass", "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glue",
+ "goat", "goddess", "gold", "good", "goose", "gorilla", "gospel", "gossip", "govern", "gown",
+ "grab", "grace", "grain", "grant", "grape", "grass", "gravity", "great", "green", "grid",
+ "grief", "grit", "grocery", "group", "grow", "grunt", "guard", "guess", "guide", "guilt",
+ "guitar", "gun", "gym", "habit", "hair", "half", "hammer", "hamster", "hand", "happy",
+ "harbor", "hard", "harsh", "harvest", "hat", "have", "hawk", "hazard", "head", "health",
+ "heart", "heavy", "hedgehog", "height", "hello", "helmet", "help", "hen", "hero", "hidden",
+ "high", "hill", "hint", "hip", "hire", "history", "hobby", "hockey", "hold", "hole", "holiday",
+ "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital", "host",
+ "hotel", "hour", "hover", "hub", "huge", "human", "humble", "humor", "hundred", "hungry",
+ "hunt", "hurdle", "hurry", "hurt", "husband", "hybrid", "ice", "icon", "idea", "identify",
+ "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", "immune",
+ "impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index",
+ "indicate", "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit",
+ "initial", "inject", "injury", "inmate", "inner", "innocent", "input", "inquiry", "insane",
+ "insect", "inside", "inspire", "install", "intact", "interest", "into", "invest", "invite",
+ "involve", "iron", "island", "isolate", "issue", "item", "ivory", "jacket", "jaguar", "jar",
+ "jazz", "jealous", "jeans", "jelly", "jewel", "job", "join", "joke", "journey", "joy", "judge",
+ "juice", "jump", "jungle", "junior", "junk", "just", "kangaroo", "keen", "keep", "ketchup",
+ "key", "kick", "kid", "kidney", "kind", "kingdom", "kiss", "kit", "kitchen", "kite", "kitten",
+ "kiwi", "knee", "knife", "knock", "know", "lab", "label", "labor", "ladder", "lady", "lake",
+ "lamp", "language", "laptop", "large", "later", "latin", "laugh", "laundry", "lava", "law",
+ "lawn", "lawsuit", "layer", "lazy", "leader", "leaf", "learn", "leave", "lecture", "left",
+ "leg", "legal", "legend", "leisure", "lemon", "lend", "length", "lens", "leopard", "lesson",
+ "letter", "level", "liar", "liberty", "library", "license", "life", "lift", "light", "like",
+ "limb", "limit", "link", "lion", "liquid", "list", "little", "live", "lizard", "load", "loan",
+ "lobster", "local", "lock", "logic", "lonely", "long", "loop", "lottery", "loud", "lounge",
+ "love", "loyal", "lucky", "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics", "machine",
+ "mad", "magic", "magnet", "maid", "mail", "main", "major", "make", "mammal", "man", "manage",
+ "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine",
+ "market", "marriage", "mask", "mass", "master", "match", "material", "math", "matrix",
+ "matter", "maximum", "maze", "meadow", "mean", "measure", "meat", "mechanic", "medal", "media",
+ "melody", "melt", "member", "memory", "mention", "menu", "mercy", "merge", "merit", "merry",
+ "mesh", "message", "metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind",
+ "minimum", "minor", "minute", "miracle", "mirror", "misery", "miss", "mistake", "mix", "mixed",
+ "mixture", "mobile", "model", "modify", "mom", "moment", "monitor", "monkey", "monster",
+ "month", "moon", "moral", "more", "morning", "mosquito", "mother", "motion", "motor",
+ "mountain", "mouse", "move", "movie", "much", "muffin", "mule", "multiply", "muscle", "museum",
+ "mushroom", "music", "must", "mutual", "myself", "mystery", "myth", "naive", "name", "napkin",
+ "narrow", "nasty", "nation", "nature", "near", "neck", "need", "negative", "neglect",
+ "neither", "nephew", "nerve", "nest", "net", "network", "neutral", "never", "news", "next",
+ "nice", "night", "noble", "noise", "nominee", "noodle", "normal", "north", "nose", "notable",
+ "note", "nothing", "notice", "novel", "now", "nuclear", "number", "nurse", "nut", "oak",
+ "obey", "object", "oblige", "obscure", "observe", "obtain", "obvious", "occur", "ocean",
+ "october", "odor", "off", "offer", "office", "often", "oil", "okay", "old", "olive", "olympic",
+ "omit", "once", "one", "onion", "online", "only", "open", "opera", "opinion", "oppose",
+ "option", "orange", "orbit", "orchard", "order", "ordinary", "organ", "orient", "original",
+ "orphan", "ostrich", "other", "outdoor", "outer", "output", "outside", "oval", "oven", "over",
+ "own", "owner", "oxygen", "oyster", "ozone", "pact", "paddle", "page", "pair", "palace",
+ "palm", "panda", "panel", "panic", "panther", "paper", "parade", "parent", "park", "parrot",
+ "party", "pass", "patch", "path", "patient", "patrol", "pattern", "pause", "pave", "payment",
+ "peace", "peanut", "pear", "peasant", "pelican", "pen", "penalty", "pencil", "people",
+ "pepper", "perfect", "permit", "person", "pet", "phone", "photo", "phrase", "physical",
+ "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill", "pilot", "pink", "pioneer",
+ "pipe", "pistol", "pitch", "pizza", "place", "planet", "plastic", "plate", "play", "please",
+ "pledge", "pluck", "plug", "plunge", "poem", "poet", "point", "polar", "pole", "police",
+ "pond", "pony", "pool", "popular", "portion", "position", "possible", "post", "potato",
+ "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare",
+ "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison",
+ "private", "prize", "problem", "process", "produce", "profit", "program", "project", "promote",
+ "proof", "property", "prosper", "protect", "proud", "provide", "public", "pudding", "pull",
+ "pulp", "pulse", "pumpkin", "punch", "pupil", "puppy", "purchase", "purity", "purpose",
+ "purse", "push", "put", "puzzle", "pyramid", "quality", "quantum", "quarter", "question",
+ "quick", "quit", "quiz", "quote", "rabbit", "raccoon", "race", "rack", "radar", "radio",
+ "rail", "rain", "raise", "rally", "ramp", "ranch", "random", "range", "rapid", "rare", "rate",
+ "rather", "raven", "raw", "razor", "ready", "real", "reason", "rebel", "rebuild", "recall",
+ "receive", "recipe", "record", "recycle", "reduce", "reflect", "reform", "refuse", "region",
+ "regret", "regular", "reject", "relax", "release", "relief", "rely", "remain", "remember",
+ "remind", "remove", "render", "renew", "rent", "reopen", "repair", "repeat", "replace",
+ "report", "require", "rescue", "resemble", "resist", "resource", "response", "result",
+ "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib",
+ "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", "ring", "riot", "ripple",
+ "risk", "ritual", "rival", "river", "road", "roast", "robot", "robust", "rocket", "romance",
+ "roof", "rookie", "room", "rose", "rotate", "rough", "round", "route", "royal", "rubber",
+ "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness", "safe", "sail",
+ "salad", "salmon", "salon", "salt", "salute", "same", "sample", "sand", "satisfy", "satoshi",
+ "sauce", "sausage", "save", "say", "scale", "scan", "scare", "scatter", "scene", "scheme",
+ "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", "script", "scrub",
+ "sea", "search", "season", "seat", "second", "secret", "section", "security", "seed", "seek",
+ "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", "service",
+ "session", "settle", "setup", "seven", "shadow", "shaft", "shallow", "share", "shed", "shell",
+ "sheriff", "shield", "shift", "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop",
+ "short", "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side",
+ "siege", "sight", "sign", "silent", "silk", "silly", "silver", "similar", "simple", "since",
+ "sing", "siren", "sister", "situate", "six", "size", "skate", "sketch", "ski", "skill", "skin",
+ "skirt", "skull", "slab", "slam", "sleep", "slender", "slice", "slide", "slight", "slim",
+ "slogan", "slot", "slow", "slush", "small", "smart", "smile", "smoke", "smooth", "snack",
+ "snake", "snap", "sniff", "snow", "soap", "soccer", "social", "sock", "soda", "soft", "solar",
+ "soldier", "solid", "solution", "solve", "someone", "song", "soon", "sorry", "sort", "soul",
+ "sound", "soup", "source", "south", "space", "spare", "spatial", "spawn", "speak", "special",
+ "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin", "spirit", "split",
+ "spoil", "sponsor", "spoon", "sport", "spot", "spray", "spread", "spring", "spy", "square",
+ "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", "stamp", "stand",
+ "start", "state", "stay", "steak", "steel", "stem", "step", "stereo", "stick", "still",
+ "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street",
+ "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit",
+ "subway", "success", "such", "sudden", "suffer", "sugar", "suggest", "suit", "summer", "sun",
+ "sunny", "sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise",
+ "surround", "survey", "suspect", "sustain", "swallow", "swamp", "swap", "swarm", "swear",
+ "sweet", "swift", "swim", "swing", "switch", "sword", "symbol", "symptom", "syrup", "system",
+ "table", "tackle", "tag", "tail", "talent", "talk", "tank", "tape", "target", "task", "taste",
+ "tattoo", "taxi", "teach", "team", "tell", "ten", "tenant", "tennis", "tent", "term", "test",
+ "text", "thank", "that", "theme", "then", "theory", "there", "they", "thing", "this",
+ "thought", "three", "thrive", "throw", "thumb", "thunder", "ticket", "tide", "tiger", "tilt",
+ "timber", "time", "tiny", "tip", "tired", "tissue", "title", "toast", "tobacco", "today",
+ "toddler", "toe", "together", "toilet", "token", "tomato", "tomorrow", "tone", "tongue",
+ "tonight", "tool", "tooth", "top", "topic", "topple", "torch", "tornado", "tortoise", "toss",
+ "total", "tourist", "toward", "tower", "town", "toy", "track", "trade", "traffic", "tragic",
+ "train", "transfer", "trap", "trash", "travel", "tray", "treat", "tree", "trend", "trial",
+ "tribe", "trick", "trigger", "trim", "trip", "trophy", "trouble", "truck", "true", "truly",
+ "trumpet", "trust", "truth", "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey",
+ "turn", "turtle", "twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical",
+ "ugly", "umbrella", "unable", "unaware", "uncle", "uncover", "under", "undo", "unfair",
+ "unfold", "unhappy", "uniform", "unique", "unit", "universe", "unknown", "unlock", "until",
+ "unusual", "unveil", "update", "upgrade", "uphold", "upon", "upper", "upset", "urban", "urge",
+ "usage", "use", "used", "useful", "useless", "usual", "utility", "vacant", "vacuum", "vague",
+ "valid", "valley", "valve", "van", "vanish", "vapor", "various", "vast", "vault", "vehicle",
+ "velvet", "vendor", "venture", "venue", "verb", "verify", "version", "very", "vessel",
+ "veteran", "viable", "vibrant", "vicious", "victory", "video", "view", "village", "vintage",
+ "violin", "virtual", "virus", "visa", "visit", "visual", "vital", "vivid", "vocal", "voice",
+ "void", "volcano", "volume", "vote", "voyage", "wage", "wagon", "wait", "walk", "wall",
+ "walnut", "want", "warfare", "warm", "warrior", "wash", "wasp", "waste", "water", "wave",
+ "way", "wealth", "weapon", "wear", "weasel", "weather", "web", "wedding", "weekend", "weird",
+ "welcome", "west", "wet", "whale", "what", "wheat", "wheel", "when", "where", "whip",
+ "whisper", "wide", "width", "wife", "wild", "will", "win", "window", "wine", "wing", "wink",
+ "winner", "winter", "wire", "wisdom", "wise", "wish", "witness", "wolf", "woman", "wonder",
+ "wood", "wool", "word", "work", "world", "worry", "worth", "wrap", "wreck", "wrestle", "wrist",
+ "write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone",
+ "zoo",
+];
diff --git a/client/libra_wallet/src/wallet_library.rs b/client/libra_wallet/src/wallet_library.rs
new file mode 100644
index 0000000000000..afcafd445532b
--- /dev/null
+++ b/client/libra_wallet/src/wallet_library.rs
@@ -0,0 +1,178 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! The following document is a minimalist version of Libra Wallet. Note that this Wallet does
+//! not promote security as the mnemonic is stored in unencrypted form. In future iterations,
+//! we will be realesing more robust Wallet implementations. It is our intention to present a
+//! foundation that is simple to understand and incrementally improve the LibraWallet
+//! implementation and it's security guarantees throughout testnet. For a more robust wallet
+//! reference, the authors suggest to audit the file of the same name in the rust-wallet crate.
+//! That file can be found here:
+//!
+//! https://github.com/rust-bitcoin/rust-wallet/blob/master/wallet/src/walletlibrary.rs
+
+use crate::{
+ error::*,
+ io_utils,
+ key_factory::{ChildNumber, KeyFactory, Seed},
+ mnemonic::Mnemonic,
+};
+use libra_crypto::hash::CryptoHash;
+use proto_conv::{FromProto, IntoProto};
+use protobuf::Message;
+use rand::{rngs::EntropyRng, Rng};
+use std::{collections::HashMap, path::Path};
+use types::{
+ account_address::AccountAddress,
+ proto::transaction::SignedTransaction as ProtoSignedTransaction,
+ transaction::{RawTransaction, RawTransactionBytes, SignedTransaction},
+};
+
+/// WalletLibrary contains all the information needed to recreate a particular wallet
+pub struct WalletLibrary {
+ mnemonic: Mnemonic,
+ key_factory: KeyFactory,
+ addr_map: HashMap,
+ key_leaf: ChildNumber,
+}
+
+impl WalletLibrary {
+ /// Constructor that generates a Mnemonic from OS randomness and subsequently instantiates an
+ /// empty WalletLibrary from that Mnemonic
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ let mut rng = EntropyRng::new();
+ let data: [u8; 32] = rng.gen();
+ let mnemonic = Mnemonic::mnemonic(&data).unwrap();
+ Self::new_from_mnemonic(mnemonic)
+ }
+
+ /// Constructor that instantiates a new WalletLibrary from Mnemonic
+ pub fn new_from_mnemonic(mnemonic: Mnemonic) -> Self {
+ let seed = Seed::new(&mnemonic, "LIBRA");
+ WalletLibrary {
+ mnemonic,
+ key_factory: KeyFactory::new(&seed).unwrap(),
+ addr_map: HashMap::new(),
+ key_leaf: ChildNumber(0),
+ }
+ }
+
+ /// Function that returns the string representation of the WalletLibrary Menmonic
+ /// NOTE: This is not secure, and in general the mnemonic should be stored in encrypted format
+ pub fn mnemonic(&self) -> String {
+ self.mnemonic.to_string()
+ }
+
+ /// Function that writes the wallet Mnemonic to file
+ /// NOTE: This is not secure, and in general the Mnemonic would need to be decrypted before it
+ /// can be written to file; otherwise the encrypted Mnemonic should be written to file
+ pub fn write_recovery(&self, output_file_path: &Path) -> Result<()> {
+ io_utils::write_recovery(&self, &output_file_path)?;
+ Ok(())
+ }
+
+ /// Recover wallet from input_file_path
+ pub fn recover(input_file_path: &Path) -> Result {
+ let wallet = io_utils::recover(&input_file_path)?;
+ Ok(wallet)
+ }
+
+ /// Get the current ChildNumber in u64 format
+ pub fn key_leaf(&self) -> u64 {
+ self.key_leaf.0
+ }
+
+ /// Function that iterates from the current key_leaf until the supplied depth
+ pub fn generate_addresses(&mut self, depth: u64) -> Result<()> {
+ let current = self.key_leaf.0;
+ if current > depth {
+ return Err(WalletError::LibraWalletGeneric(
+ "Addresses already generated up to the supplied depth".to_string(),
+ ));
+ }
+ while self.key_leaf != ChildNumber(depth) {
+ let _ = self.new_address();
+ }
+ Ok(())
+ }
+
+ /// Function that allows to get the address of a particular key at a certain ChildNumber
+ pub fn new_address_at_child_number(
+ &mut self,
+ child_number: ChildNumber,
+ ) -> Result {
+ let child = self.key_factory.private_child(child_number)?;
+ child.get_address()
+ }
+
+ /// Function that generates a new key and adds it to the addr_map and subsequently returns the
+ /// AccountAddress associated to the PrivateKey, along with it's ChildNumber
+ pub fn new_address(&mut self) -> Result<(AccountAddress, ChildNumber)> {
+ let child = self.key_factory.private_child(self.key_leaf)?;
+ let address = child.get_address()?;
+ let child = self.key_leaf;
+ self.key_leaf.increment();
+ match self.addr_map.insert(address, child) {
+ Some(_) => Err(WalletError::LibraWalletGeneric(
+ "This address is already in your wallet".to_string(),
+ )),
+ None => Ok((address, child)),
+ }
+ }
+
+ /// Returns a list of all addresses controlled by this wallet that are currently held by the
+ /// addr_map
+ pub fn get_addresses(&self) -> Result> {
+ let mut ret = Vec::with_capacity(self.addr_map.len());
+ let rev_map = self
+ .addr_map
+ .iter()
+ .map(|(&k, &v)| (v.as_ref().to_owned(), k.to_owned()))
+ .collect::>();
+ for i in 0..self.addr_map.len() as u64 {
+ match rev_map.get(&i) {
+ Some(account_address) => {
+ ret.push(*account_address);
+ }
+ None => {
+ return Err(WalletError::LibraWalletGeneric(format!(
+ "Child num {} not exist while depth is {}",
+ i,
+ self.addr_map.len()
+ )))
+ }
+ }
+ }
+ Ok(ret)
+ }
+
+ /// Simple public function that allows to sign a Libra RawTransaction with the PrivateKey
+ /// associated to a particular AccountAddress. If the PrivateKey associated to an
+ /// AccountAddress is not contained in the addr_map, then this function will return an Error
+ pub fn sign_txn(
+ &self,
+ addr: &AccountAddress,
+ txn: RawTransaction,
+ ) -> Result {
+ if let Some(child) = self.addr_map.get(addr) {
+ let raw_bytes = txn.into_proto().write_to_bytes()?;
+ let txn_hashvalue = RawTransactionBytes(&raw_bytes).hash();
+
+ let child_key = self.key_factory.private_child(child.clone())?;
+ let signature = child_key.sign(txn_hashvalue);
+ let public_key = child_key.get_public();
+
+ let mut signed_txn = ProtoSignedTransaction::new();
+ signed_txn.set_raw_txn_bytes(raw_bytes.to_vec());
+ signed_txn.set_sender_public_key(public_key.to_bytes().to_vec());
+ signed_txn.set_sender_signature(signature.to_bytes().to_vec());
+
+ Ok(SignedTransaction::from_proto(signed_txn)?)
+ } else {
+ Err(WalletError::LibraWalletGeneric(
+ "Well, that address is nowhere to be found... This is awkward".to_string(),
+ ))
+ }
+ }
+}
diff --git a/client/src/account_commands.rs b/client/src/account_commands.rs
new file mode 100644
index 0000000000000..ef92e49654ffc
--- /dev/null
+++ b/client/src/account_commands.rs
@@ -0,0 +1,148 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{client_proxy::ClientProxy, commands::*};
+
+/// Major command for account related operations.
+pub struct AccountCommand {}
+
+impl Command for AccountCommand {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["account", "a"]
+ }
+ fn get_description(&self) -> &'static str {
+ "Account operations"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ let commands: Vec> = vec![
+ Box::new(AccountCommandCreate {}),
+ Box::new(AccountCommandListAccounts {}),
+ Box::new(AccountCommandRecoverWallet {}),
+ Box::new(AccountCommandWriteRecovery {}),
+ Box::new(AccountCommandMint {}),
+ ];
+
+ subcommand_execute(¶ms[0], commands, client, ¶ms[1..]);
+ }
+}
+
+/// Sub command to create a random account. The account will not be saved on chain.
+pub struct AccountCommandCreate {}
+
+impl Command for AccountCommandCreate {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["create", "c"]
+ }
+ fn get_description(&self) -> &'static str {
+ "Create an account. Returns reference ID to use in other operations"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Creating/retrieving next account from wallet");
+ match client.create_next_account(¶ms) {
+ Ok(account_data) => println!(
+ "Created/retrieved account #{} address {}",
+ account_data.index,
+ hex::encode(account_data.address)
+ ),
+ Err(e) => report_error("Error creating account", e),
+ }
+ }
+}
+
+/// Sub command to recover wallet from the file specified.
+pub struct AccountCommandRecoverWallet {}
+
+impl Command for AccountCommandRecoverWallet {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["recover", "r"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ ""
+ }
+ fn get_description(&self) -> &'static str {
+ "Recover Libra wallet from the file path"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Recovering Wallet");
+ match client.recover_wallet_accounts(¶ms) {
+ Ok(account_data) => {
+ println!(
+ "Wallet recovered and the first {} child accounts were derived",
+ account_data.len()
+ );
+ for data in account_data {
+ println!("#{} address {}", data.index, hex::encode(data.address));
+ }
+ }
+ Err(e) => report_error("Error recovering Libra wallet", e),
+ }
+ }
+}
+
+/// Sub command to backup wallet to the file specified.
+pub struct AccountCommandWriteRecovery {}
+
+impl Command for AccountCommandWriteRecovery {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["write", "w"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ ""
+ }
+ fn get_description(&self) -> &'static str {
+ "Save Libra wallet mnemonic recovery seed to disk"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Saving Libra wallet mnemonic recovery seed to disk");
+ match client.write_recovery(¶ms) {
+ Ok(_) => println!("Saved mnemonic seed to disk"),
+ Err(e) => report_error("Error writing mnemonic recovery seed to file", e),
+ }
+ }
+}
+
+/// Sub command to list all accounts information.
+pub struct AccountCommandListAccounts {}
+
+impl Command for AccountCommandListAccounts {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["list", "la"]
+ }
+ fn get_description(&self) -> &'static str {
+ "Print all accounts that were created or loaded"
+ }
+ fn execute(&self, client: &mut ClientProxy, _params: &[&str]) {
+ client.print_all_accounts();
+ }
+}
+
+/// Sub command to mint account.
+pub struct AccountCommandMint {}
+
+impl Command for AccountCommandMint {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["mint", "mintb", "m", "mb"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "| "
+ }
+ fn get_description(&self) -> &'static str {
+ "Mint coins to the account. Suffix 'b' is for blocking"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Minting coins");
+ let is_blocking = blocking_cmd(params[0]);
+ match client.mint_coins(¶ms, is_blocking) {
+ Ok(_) => {
+ if is_blocking {
+ println!("Finished minting!");
+ } else {
+ // If this value is updated, it must also be changed in
+ // setup_scripts/docker/mint/server.py
+ println!("Mint request submitted");
+ }
+ }
+ Err(e) => report_error("Error minting coins", e),
+ }
+ }
+}
diff --git a/client/src/client_proxy.rs b/client/src/client_proxy.rs
new file mode 100644
index 0000000000000..10ecc8a07331f
--- /dev/null
+++ b/client/src/client_proxy.rs
@@ -0,0 +1,890 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{commands::*, grpc_client::GRPCClient, AccountData};
+use admission_control_proto::proto::admission_control::SubmitTransactionRequest;
+use chrono::Utc;
+use config::trusted_peers::TrustedPeersConfig;
+use crypto::{
+ hash::CryptoHash,
+ signing::{sign_message, KeyPair, PrivateKey},
+};
+use failure::prelude::*;
+use futures::{future::Future, stream::Stream};
+use hyper;
+use libra_wallet::{io_utils, wallet_library::WalletLibrary};
+use num_traits::{
+ cast::{FromPrimitive, ToPrimitive},
+ identities::Zero,
+};
+use proto_conv::IntoProto;
+use protobuf::Message;
+use rust_decimal::Decimal;
+use std::{
+ collections::HashMap,
+ convert::TryFrom,
+ fs,
+ io::{stdout, Write},
+ path::Path,
+ str::FromStr,
+ sync::Arc,
+ thread, time,
+};
+use tokio::{self, runtime::Runtime};
+use types::{
+ access_path::AccessPath,
+ account_address::{AccountAddress, ADDRESS_LENGTH},
+ account_config::{account_received_event_path, account_sent_event_path, association_address},
+ account_state_blob::{AccountStateBlob, AccountStateWithProof},
+ contract_event::{ContractEvent, EventWithProof},
+ transaction::{Program, RawTransaction, RawTransactionBytes, SignedTransaction, Version},
+ validator_verifier::ValidatorVerifier,
+};
+
+const CLIENT_WALLET_MNEMONIC_FILE: &str = "client.mnemonic";
+const GAS_UNIT_PRICE: u64 = 0;
+const MAX_GAS_AMOUNT: u64 = 10_000;
+const TX_EXPIRATION: i64 = 100;
+
+/// Enum used for error formatting.
+#[derive(Debug)]
+enum InputType {
+ Bool,
+ UnsignedInt,
+ Usize,
+}
+
+/// Account data is stored in a map and referenced by an index.
+#[derive(Debug)]
+pub struct AddressAndIndex {
+ /// Address of the account.
+ pub address: AccountAddress,
+ /// The account_ref_id of this account in client.
+ pub index: usize,
+}
+
+/// Used to return the sequence and sender account index submitted for a transfer
+pub struct IndexAndSequence {
+ /// Index/key of the account in TestClient::accounts vector.
+ pub account_index: usize,
+ /// Sequence number of the account.
+ pub sequence_number: u64,
+}
+
+/// Client used to test
+pub struct ClientProxy {
+ /// client for admission control interface.
+ pub client: GRPCClient,
+ /// Created accounts.
+ pub accounts: Vec,
+ /// Address to account_ref_id map.
+ address_to_ref_id: HashMap,
+ /// We use an incrementing index to reference the accounts we create so it is easier
+ /// to use this from the command line. This is the index we are currently at.
+ cur_account_index: usize,
+ /// Host that operates a faucet service
+ faucet_server: String,
+ /// Account used for mint operations.
+ pub faucet_account: Option,
+ /// Wallet library managing user accounts.
+ pub wallet: WalletLibrary,
+}
+
+impl ClientProxy {
+ /// Construct a new TestClient.
+ pub fn new(
+ host: &str,
+ ac_port: &str,
+ validator_set_file: &str,
+ faucet_account_file: &str,
+ faucet_server: Option,
+ mnemonic_file: Option,
+ ) -> Result {
+ let validators_config = TrustedPeersConfig::load_config(Path::new(validator_set_file));
+ let validators = validators_config.get_trusted_consensus_peers();
+ ensure!(
+ !validators.is_empty(),
+ "Not able to load validators from trusted peers config!"
+ );
+ // Total 3f + 1 validators, 2f + 1 correct signatures are required.
+ // If < 4 validators, all validators have to agree.
+ let quorum_size = validators.len() * 2 / 3 + 1;
+ let validator_verifier = Arc::new(ValidatorVerifier::new(validators, quorum_size));
+ let client = GRPCClient::new(host, ac_port, validator_verifier)?;
+
+ let accounts = vec![];
+
+ // If we have a faucet account file, then load it to get the keypair
+ let faucet_account = if faucet_account_file.is_empty() {
+ None
+ } else {
+ let faucet_account_keypair: KeyPair =
+ ClientProxy::load_faucet_account_file(faucet_account_file);
+ let faucet_address = association_address();
+
+ let faucet_seq_number = client.get_sequence_number(faucet_address);
+
+ let mut faucet_account_data = Self::create_account(
+ faucet_account_keypair.private_key().clone(),
+ faucet_seq_number.unwrap_or(0),
+ );
+ faucet_account_data.address = faucet_address;
+ // Load the keypair from file
+ Some(faucet_account_data)
+ };
+
+ let faucet_server = match faucet_server {
+ Some(server) => server.to_string(),
+ None => host.replace("ac", "faucet"),
+ };
+
+ let address_to_ref_id = accounts
+ .iter()
+ .enumerate()
+ .map(|(ref_id, acc_data): (usize, &AccountData)| (acc_data.address, ref_id))
+ .collect::>();
+ let cur_account_index = accounts.len();
+
+ Ok(ClientProxy {
+ client,
+ accounts,
+ address_to_ref_id,
+ cur_account_index,
+ faucet_server,
+ faucet_account,
+ wallet: Self::get_libra_wallet(mnemonic_file)?,
+ })
+ }
+
+ /// Returns the account index that should be used by user to reference this account
+ pub fn create_next_account(&mut self, space_delim_strings: &[&str]) -> Result {
+ ensure!(
+ space_delim_strings.len() == 1,
+ "Invalid number of arguments to account creation"
+ );
+ let (address, _) = self.wallet.new_address()?;
+
+ // Sync with validator for account sequence number in case it is already created on chain.
+ // This assumes we have a very low probability of mnemonic word conflict.
+ let sequence_number = self.client.get_sequence_number(address).unwrap_or(0);
+
+ let account_data = AccountData {
+ address,
+ key_pair: None,
+ sequence_number,
+ };
+
+ Ok(self.insert_account_data(account_data))
+ }
+
+ /// Print index and address of all accounts.
+ pub fn print_all_accounts(&self) {
+ if self.accounts.is_empty() {
+ println!("No user accounts");
+ } else {
+ for (ref index, ref account) in self.accounts.iter().enumerate() {
+ println!(
+ "User account index: {}, address: {}, sequence number: {}",
+ index,
+ hex::encode(&account.address),
+ account.sequence_number,
+ );
+ }
+ }
+
+ if let Some(faucet_account) = &self.faucet_account {
+ println!(
+ "Faucet account address: {}, sequence_number: {}",
+ hex::encode(&faucet_account.address),
+ faucet_account.sequence_number,
+ );
+ }
+ }
+
+ /// Clone all accounts held in the client.
+ pub fn copy_all_accounts(&self) -> Vec {
+ self.accounts.clone()
+ }
+
+ /// Set the account of this client instance.
+ pub fn set_accounts(&mut self, accounts: Vec) -> Vec {
+ self.accounts.clear();
+ self.address_to_ref_id.clear();
+ self.cur_account_index = 0;
+ let mut ret = vec![];
+ for data in accounts {
+ ret.push(self.insert_account_data(data));
+ }
+ ret
+ }
+
+ /// Get balance from validator for the account sepcified.
+ pub fn get_balance(&mut self, space_delim_strings: &[&str]) -> Result {
+ ensure!(
+ space_delim_strings.len() == 2,
+ "Invalid number of arguments for getting balance"
+ );
+ let account = self.get_account_address_from_parameter(space_delim_strings[1])?;
+
+ self.client
+ .get_balance(account)
+ .map(|val| val as f64 / 1_000_000.)
+ }
+
+ /// Get the latest sequence number from validator for the account specified.
+ pub fn get_sequence_number(&mut self, space_delim_strings: &[&str]) -> Result {
+ ensure!(
+ space_delim_strings.len() == 2 || space_delim_strings.len() == 3,
+ "Invalid number of arguments for getting sequence number"
+ );
+ let account_address = self.get_account_address_from_parameter(space_delim_strings[1])?;
+ let sequence_number = self.client.get_sequence_number(account_address)?;
+
+ let reset_sequence_number = if space_delim_strings.len() == 3 {
+ space_delim_strings[2].parse::().map_err(|error| {
+ format_parse_data_error(
+ "reset_sequence_number",
+ InputType::Bool,
+ space_delim_strings[2],
+ error,
+ )
+ })?
+ } else {
+ false
+ };
+ if reset_sequence_number {
+ let mut account = self.mut_account_from_parameter(space_delim_strings[1])?;
+ // Set sequence_number to latest one.
+ account.sequence_number = sequence_number;
+ }
+ Ok(sequence_number)
+ }
+
+ /// Mints coins for the receiver specified.
+ pub fn mint_coins(&mut self, space_delim_strings: &[&str], is_blocking: bool) -> Result<()> {
+ ensure!(
+ space_delim_strings.len() == 3,
+ "Invalid number of arguments for mint"
+ );
+ let receiver = self.get_account_address_from_parameter(space_delim_strings[1])?;
+ let num_coins = Self::convert_to_micro_libras(space_delim_strings[2])?;
+
+ match self.faucet_account {
+ Some(_) => self.mint_coins_with_local_faucet_account(&receiver, num_coins, is_blocking),
+ None => self.mint_coins_with_faucet_service(&receiver, num_coins, is_blocking),
+ }
+ }
+
+ /// Waits for the next transaction for a specific address and prints it
+ pub fn wait_for_transaction(&mut self, account: AccountAddress, sequence_number: u64) {
+ let mut max_iterations = 50;
+ print!("[waiting ");
+ loop {
+ stdout().flush().unwrap();
+ max_iterations -= 1;
+
+ match self.client.get_sequence_number(account) {
+ Ok(chain_seq_number) => {
+ if chain_seq_number == sequence_number {
+ println!(
+ "\nTransaction completed, found sequence number {}",
+ chain_seq_number
+ );
+ break;
+ }
+ print!("*");
+ }
+ Err(e) => {
+ if max_iterations == 0 {
+ panic!("wait_for_transaction timeout: {}", e);
+ } else {
+ print!(".");
+ }
+ }
+ }
+
+ thread::sleep(time::Duration::from_millis(1000));
+ }
+ }
+
+ /// Transfer num_coins from sender account to receiver. If is_blocking = true,
+ /// it will keep querying validator till the sequence number is bumped up in validator.
+ pub fn transfer_coins_int(
+ &mut self,
+ sender_account_ref_id: usize,
+ receiver_address: &AccountAddress,
+ num_coins: u64,
+ gas_unit_price: Option,
+ max_gas_amount: Option,
+ is_blocking: bool,
+ ) -> Result {
+ let sender_address;
+ let sender_sequence;
+ let resp;
+ {
+ let sender = self
+ .accounts
+ .get_mut(sender_account_ref_id)
+ .ok_or_else(|| {
+ format_err!("Unable to find sender account: {}", sender_account_ref_id)
+ })?;
+
+ let program = vm_genesis::encode_transfer_program(&receiver_address, num_coins);
+ let req = Self::create_submit_transaction_req(
+ program,
+ sender,
+ &self.wallet,
+ gas_unit_price, /* gas_unit_price */
+ max_gas_amount, /* max_gas_amount */
+ )?;
+ resp = self.client.submit_transaction(sender, &req);
+ sender_address = sender.address;
+ sender_sequence = sender.sequence_number;
+ }
+
+ if is_blocking {
+ self.wait_for_transaction(sender_address, sender_sequence);
+ }
+
+ resp.map(|_| IndexAndSequence {
+ account_index: sender_account_ref_id,
+ sequence_number: sender_sequence - 1,
+ })
+ }
+
+ /// Transfers coins from sender to receiver.
+ pub fn transfer_coins(
+ &mut self,
+ space_delim_strings: &[&str],
+ is_blocking: bool,
+ ) -> Result {
+ ensure!(
+ space_delim_strings.len() >= 4 && space_delim_strings.len() <= 6,
+ "Invalid number of arguments for transfer"
+ );
+
+ let sender_account_address =
+ self.get_account_address_from_parameter(space_delim_strings[1])?;
+ let receiver_address = self.get_account_address_from_parameter(space_delim_strings[2])?;
+
+ let num_coins = Self::convert_to_micro_libras(space_delim_strings[3])?;
+
+ let gas_unit_price = if space_delim_strings.len() > 4 {
+ Some(space_delim_strings[4].parse::().map_err(|error| {
+ format_parse_data_error(
+ "gas_unit_price",
+ InputType::UnsignedInt,
+ space_delim_strings[4],
+ error,
+ )
+ })?)
+ } else {
+ None
+ };
+
+ let max_gas_amount = if space_delim_strings.len() > 5 {
+ Some(space_delim_strings[5].parse::().map_err(|error| {
+ format_parse_data_error(
+ "max_gas_amount",
+ InputType::UnsignedInt,
+ space_delim_strings[5],
+ error,
+ )
+ })?)
+ } else {
+ None
+ };
+
+ let sender_account_ref_id = *self
+ .address_to_ref_id
+ .get(&sender_account_address)
+ .ok_or_else(|| {
+ format_err!(
+ "Unable to find existing managing account by address: {}, to see all existing \
+ accounts, run: 'account list'",
+ sender_account_address
+ )
+ })?;
+
+ self.transfer_coins_int(
+ sender_account_ref_id,
+ &receiver_address,
+ num_coins,
+ gas_unit_price,
+ max_gas_amount,
+ is_blocking,
+ )
+ }
+
+ /// Get the latest account state from validator.
+ pub fn get_latest_account_state(
+ &mut self,
+ space_delim_strings: &[&str],
+ ) -> Result<(Option, Version)> {
+ ensure!(
+ space_delim_strings.len() == 2,
+ "Invalid number of arguments to get latest account state"
+ );
+ let account = self.get_account_address_from_parameter(space_delim_strings[1])?;
+ self.client.get_account_blob(account)
+ }
+
+ /// Get committed txn by account and sequnce number.
+ pub fn get_committed_txn_by_acc_seq(
+ &mut self,
+ space_delim_strings: &[&str],
+ ) -> Result>)>> {
+ ensure!(
+ space_delim_strings.len() == 4,
+ "Invalid number of arguments to get transaction by account and sequence number"
+ );
+ let account = self.get_account_address_from_parameter(space_delim_strings[1])?;
+ let sequence_number = space_delim_strings[2].parse::().map_err(|error| {
+ format_parse_data_error(
+ "account_sequence_number",
+ InputType::UnsignedInt,
+ space_delim_strings[2],
+ error,
+ )
+ })?;
+
+ let fetch_events = space_delim_strings[3].parse::().map_err(|error| {
+ format_parse_data_error(
+ "fetch_events",
+ InputType::Bool,
+ space_delim_strings[3],
+ error,
+ )
+ })?;
+
+ self.client
+ .get_txn_by_acc_seq(account, sequence_number, fetch_events)
+ }
+
+ /// Get committed txn by account and sequence number
+ pub fn get_committed_txn_by_range(
+ &mut self,
+ space_delim_strings: &[&str],
+ ) -> Result>)>> {
+ ensure!(
+ space_delim_strings.len() == 4,
+ "Invalid number of arguments to get transaction by range"
+ );
+ let start_version = space_delim_strings[1].parse::().map_err(|error| {
+ format_parse_data_error(
+ "start_version",
+ InputType::UnsignedInt,
+ space_delim_strings[1],
+ error,
+ )
+ })?;
+ let limit = space_delim_strings[2].parse::().map_err(|error| {
+ format_parse_data_error(
+ "limit",
+ InputType::UnsignedInt,
+ space_delim_strings[2],
+ error,
+ )
+ })?;
+ let fetch_events = space_delim_strings[3].parse::().map_err(|error| {
+ format_parse_data_error(
+ "fetch_events",
+ InputType::Bool,
+ space_delim_strings[3],
+ error,
+ )
+ })?;
+
+ self.client
+ .get_txn_by_range(start_version, limit, fetch_events)
+ }
+
+ /// Get account address from parameter. If the parameter is string of address, try to convert
+ /// it to address, otherwise, try to convert to u64 and looking at TestClient::accounts.
+ pub fn get_account_address_from_parameter(&self, para: &str) -> Result {
+ match is_address(para) {
+ true => ClientProxy::address_from_strings(para),
+ false => {
+ let account_ref_id = para.parse::().map_err(|error| {
+ format_parse_data_error(
+ "account_reference_id/account_address",
+ InputType::Usize,
+ para,
+ error,
+ )
+ })?;
+ let account_data = self.accounts.get(account_ref_id).ok_or_else(|| {
+ format_err!(
+ "Unable to find account by account reference id: {}, to see all existing \
+ accounts, run: 'account list'",
+ account_ref_id
+ )
+ })?;
+ Ok(account_data.address)
+ }
+ }
+ }
+
+ /// Get events by account and event type with start sequence number and limit.
+ pub fn get_events_by_account_and_type(
+ &mut self,
+ space_delim_strings: &[&str],
+ ) -> Result<(Vec, Option)> {
+ ensure!(
+ space_delim_strings.len() == 6,
+ "Invalid number of arguments to get events by access path"
+ );
+ let account = self.get_account_address_from_parameter(space_delim_strings[1])?;
+ let path = match space_delim_strings[2] {
+ "sent" => account_sent_event_path(),
+ "received" => account_received_event_path(),
+ _ => bail!(
+ "Unknown event type: {:?}, only sent and received are supported",
+ space_delim_strings[2]
+ ),
+ };
+ let access_path = AccessPath::new(account, path);
+ let start_seq_number = space_delim_strings[3].parse::().map_err(|error| {
+ format_parse_data_error(
+ "start_seq_number",
+ InputType::UnsignedInt,
+ space_delim_strings[3],
+ error,
+ )
+ })?;
+ let ascending = space_delim_strings[4].parse::().map_err(|error| {
+ format_parse_data_error("ascending", InputType::Bool, space_delim_strings[4], error)
+ })?;
+ let limit = space_delim_strings[5].parse::().map_err(|error| {
+ format_parse_data_error(
+ "start_seq_number",
+ InputType::UnsignedInt,
+ space_delim_strings[3],
+ error,
+ )
+ })?;
+ self.client
+ .get_events_by_access_path(access_path, start_seq_number, ascending, limit)
+ }
+
+ /// Write mnemonic recover to the file specified.
+ pub fn write_recovery(&self, space_delim_strings: &[&str]) -> Result<()> {
+ ensure!(
+ space_delim_strings.len() == 2,
+ "Invalid number of arguments for writing recovery"
+ );
+
+ self.wallet
+ .write_recovery(&Path::new(space_delim_strings[1]))?;
+ Ok(())
+ }
+
+ /// Recover wallet accounts from file and return vec<(account_address, index)>.
+ pub fn recover_wallet_accounts(
+ &mut self,
+ space_delim_strings: &[&str],
+ ) -> Result> {
+ ensure!(
+ space_delim_strings.len() == 2,
+ "Invalid number of arguments for recovering wallets"
+ );
+
+ let wallet = WalletLibrary::recover(&Path::new(space_delim_strings[1]))?;
+ let wallet_addresses = wallet.get_addresses()?;
+ let mut account_data = Vec::new();
+ for address in wallet_addresses {
+ let sequence_number = self.client.get_sequence_number(address)?;
+ account_data.push(AccountData {
+ address,
+ key_pair: None,
+ sequence_number,
+ });
+ }
+ self.set_wallet(wallet);
+ // Clear current cached AccountData as we always swap the entire wallet completely.
+ Ok(self.set_accounts(account_data))
+ }
+
+ /// Insert the account data to Client::accounts and return its address and index.s
+ pub fn insert_account_data(&mut self, account_data: AccountData) -> AddressAndIndex {
+ let address = account_data.address;
+
+ self.accounts.push(account_data);
+ self.address_to_ref_id
+ .insert(address, self.cur_account_index);
+
+ self.cur_account_index += 1;
+
+ AddressAndIndex {
+ address,
+ index: self.cur_account_index - 1,
+ }
+ }
+
+ /// Test gRPC client connection with validator.
+ pub fn test_validator_connection(&self) -> Result<()> {
+ self.client.get_with_proof_sync(vec![])?;
+ Ok(())
+ }
+
+ fn get_libra_wallet(mnemonic_file: Option) -> Result {
+ let wallet_recovery_file_path = if let Some(input_mnemonic_word) = mnemonic_file {
+ Path::new(&input_mnemonic_word).to_path_buf()
+ } else {
+ let mut file_path = std::env::current_dir()?;
+ file_path.push(CLIENT_WALLET_MNEMONIC_FILE);
+ file_path
+ };
+
+ let wallet = if let Ok(recovered_wallet) = io_utils::recover(&wallet_recovery_file_path) {
+ recovered_wallet
+ } else {
+ let new_wallet = WalletLibrary::new();
+ new_wallet.write_recovery(&wallet_recovery_file_path)?;
+ new_wallet
+ };
+ Ok(wallet)
+ }
+
+ /// Set wallet instance used by this client.
+ fn set_wallet(&mut self, wallet: WalletLibrary) {
+ self.wallet = wallet;
+ }
+
+ fn load_faucet_account_file(faucet_account_file: &str) -> KeyPair {
+ match fs::read(faucet_account_file) {
+ Ok(data) => {
+ bincode::deserialize(&data[..]).expect("Unable to deserialize faucet account file")
+ }
+ Err(e) => {
+ panic!(
+ "Unable to read faucet account file: {}, {}",
+ faucet_account_file, e
+ );
+ }
+ }
+ }
+
+ fn address_from_strings(data: &str) -> Result {
+ let account_vec: Vec = hex::decode(data.parse::()?)?;
+ ensure!(
+ account_vec.len() == ADDRESS_LENGTH,
+ "The address {:?} is of invalid length. Addresses must be 32-bytes long"
+ );
+ let account = match AccountAddress::try_from(&account_vec[..]) {
+ Ok(address) => address,
+ Err(error) => bail!(
+ "The address {:?} is invalid, error: {:?}",
+ &account_vec,
+ error,
+ ),
+ };
+ Ok(account)
+ }
+
+ fn mint_coins_with_local_faucet_account(
+ &mut self,
+ receiver: &AccountAddress,
+ num_coins: u64,
+ is_blocking: bool,
+ ) -> Result<()> {
+ ensure!(self.faucet_account.is_some(), "No faucet account loaded");
+ let mut sender = self.faucet_account.as_mut().unwrap();
+ let sender_address = sender.address;
+ let program = vm_genesis::encode_mint_program(&receiver, num_coins);
+ let req = Self::create_submit_transaction_req(
+ program,
+ sender,
+ &self.wallet,
+ None, /* gas_unit_price */
+ None, /* max_gas_amount */
+ )?;
+ let resp = self.client.submit_transaction(&mut sender, &req);
+ if is_blocking {
+ self.wait_for_transaction(
+ sender_address,
+ self.faucet_account.as_ref().unwrap().sequence_number,
+ );
+ }
+ resp
+ }
+
+ fn mint_coins_with_faucet_service(
+ &mut self,
+ receiver: &AccountAddress,
+ num_coins: u64,
+ is_blocking: bool,
+ ) -> Result<()> {
+ let mut runtime = Runtime::new().unwrap();
+ let client = hyper::Client::new();
+
+ let url = format!(
+ "http://{}?amount={}&address={:?}",
+ self.faucet_server, num_coins, receiver
+ )
+ .parse::()?;
+
+ let response = runtime.block_on(client.get(url))?;
+ let status_code = response.status();
+ let body = response.into_body().concat2().wait()?;
+ let raw_data = std::str::from_utf8(&body)?;
+
+ if status_code != 200 {
+ return Err(format_err!(
+ "Failed to query remote faucet server[status={}]: {:?}",
+ status_code,
+ raw_data,
+ ));
+ }
+ let sequence_number = raw_data.parse::()?;
+ if is_blocking {
+ self.wait_for_transaction(AccountAddress::new([0; 32]), sequence_number);
+ }
+ Ok(())
+ }
+
+ fn convert_to_micro_libras(input: &str) -> Result {
+ ensure!(!input.is_empty(), "Empty input not allowed for libra unit");
+ // This is not supposed to panic as it is used as constant here.
+ let max_value = Decimal::from_u64(std::u64::MAX).unwrap() / Decimal::new(1_000_000, 0);
+ let scale = input.find('.').unwrap_or(input.len() - 1);
+ ensure!(
+ scale <= 14,
+ "Input value is too big: {:?}, max: {:?}",
+ input,
+ max_value
+ );
+ let original = Decimal::from_str(input)?;
+ ensure!(
+ original <= max_value,
+ "Input value is too big: {:?}, max: {:?}",
+ input,
+ max_value
+ );
+ let value = original * Decimal::new(1_000_000, 0);
+ ensure!(value.fract().is_zero(), "invalid value");
+ value.to_u64().ok_or_else(|| format_err!("invalid value"))
+ }
+
+ fn create_submit_transaction_req(
+ program: Program,
+ sender_account: &mut AccountData,
+ wallet: &WalletLibrary,
+ gas_unit_price: Option,
+ max_gas_amount: Option,
+ ) -> Result {
+ let raw_txn = RawTransaction::new(
+ sender_account.address,
+ sender_account.sequence_number,
+ program,
+ max_gas_amount.unwrap_or(MAX_GAS_AMOUNT),
+ gas_unit_price.unwrap_or(GAS_UNIT_PRICE),
+ std::time::Duration::new((Utc::now().timestamp() + TX_EXPIRATION) as u64, 0),
+ );
+
+ let signed_txn = match &sender_account.key_pair {
+ Some(key_pair) => {
+ let bytes = raw_txn.clone().into_proto().write_to_bytes()?;
+ let hash = RawTransactionBytes(&bytes).hash();
+ let signature = sign_message(hash, &key_pair.private_key())?;
+
+ SignedTransaction::new_for_test(raw_txn, key_pair.public_key(), signature)
+ }
+ None => wallet.sign_txn(&sender_account.address, raw_txn)?,
+ };
+
+ let mut req = SubmitTransactionRequest::new();
+ req.set_signed_txn(signed_txn.into_proto());
+ Ok(req)
+ }
+
+ fn mut_account_from_parameter(&mut self, para: &str) -> Result<&mut AccountData> {
+ let account_ref_id = match is_address(para) {
+ true => {
+ let account_address = ClientProxy::address_from_strings(para)?;
+ *self
+ .address_to_ref_id
+ .get(&account_address)
+ .ok_or_else(|| {
+ format_err!(
+ "Unable to find local account by address: {:?}",
+ account_address
+ )
+ })?
+ }
+ false => para.parse::()?,
+ };
+ let account_data = self
+ .accounts
+ .get_mut(account_ref_id)
+ .ok_or_else(|| format_err!("Unable to find account by ref id: {}", account_ref_id))?;
+ Ok(account_data)
+ }
+
+ /// Populate a AccountData struct using private key and sequence number.
+ fn create_account(private_key: PrivateKey, sequence_number: u64) -> AccountData {
+ let keypair = KeyPair::new(private_key);
+ let address: AccountAddress = keypair.public_key().into();
+ AccountData {
+ address,
+ key_pair: Some(keypair),
+ sequence_number,
+ }
+ }
+}
+
+fn format_parse_data_error(
+ field: &str,
+ input_type: InputType,
+ value: &str,
+ error: T,
+) -> Error {
+ format_err!(
+ "Unable to parse input for {} - \
+ please enter an {:?}. Input was: {}, error: {:?}",
+ field,
+ input_type,
+ value,
+ error
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::client_proxy::ClientProxy;
+ use proptest::prelude::*;
+
+ #[test]
+ fn test_micro_libra_conversion() {
+ assert!(ClientProxy::convert_to_micro_libras("").is_err());
+ assert!(ClientProxy::convert_to_micro_libras("-11").is_err());
+ assert!(ClientProxy::convert_to_micro_libras("abc").is_err());
+ assert!(ClientProxy::convert_to_micro_libras("11111112312321312321321321").is_err());
+ assert!(ClientProxy::convert_to_micro_libras("0").is_ok());
+ assert!(ClientProxy::convert_to_micro_libras("1").is_ok());
+ assert!(ClientProxy::convert_to_micro_libras("0.1").is_ok());
+ assert!(ClientProxy::convert_to_micro_libras("1.1").is_ok());
+ // Max of micro libra is u64::MAX (18446744073709551615).
+ assert!(ClientProxy::convert_to_micro_libras("18446744073709.551615").is_ok());
+ assert!(ClientProxy::convert_to_micro_libras("184467440737095.51615").is_err());
+ assert!(ClientProxy::convert_to_micro_libras("18446744073709.551616").is_err());
+ }
+
+ proptest! {
+ // Proptest is used to verify that the conversion will not panic with random input.
+ #[test]
+ fn test_micro_libra_conversion_random_string(req in any::()) {
+ let _res = ClientProxy::convert_to_micro_libras(&req);
+ }
+ #[test]
+ fn test_micro_libra_conversion_random_f64(req in any::()) {
+ let req_str = req.to_string();
+ let _res = ClientProxy::convert_to_micro_libras(&req_str);
+ }
+ #[test]
+ fn test_micro_libra_conversion_random_u64(req in any::()) {
+ let req_str = req.to_string();
+ let _res = ClientProxy::convert_to_micro_libras(&req_str);
+ }
+ }
+}
diff --git a/client/src/commands.rs b/client/src/commands.rs
new file mode 100644
index 0000000000000..5973bb6a21740
--- /dev/null
+++ b/client/src/commands.rs
@@ -0,0 +1,138 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{
+ account_commands::AccountCommand, client_proxy::ClientProxy, query_commands::QueryCommand,
+ transfer_commands::TransferCommand,
+};
+
+use failure::prelude::*;
+use metrics::counters::*;
+use std::{collections::HashMap, sync::Arc};
+use types::account_address::ADDRESS_LENGTH;
+
+/// Print the error and bump up error counter.
+pub fn report_error(msg: &str, e: Error) {
+ println!("[ERROR] {}: {}", msg, pretty_format_error(e));
+ COUNTER_CLIENT_ERRORS.inc();
+}
+
+fn pretty_format_error(e: Error) -> String {
+ if let Some(grpc_error) = e.downcast_ref::() {
+ if let grpcio::Error::RpcFailure(grpc_rpc_failure) = grpc_error {
+ match grpc_rpc_failure.status {
+ grpcio::RpcStatusCode::Unavailable | grpcio::RpcStatusCode::DeadlineExceeded => {
+ return "Server unavailable, please retry and/or check \
+ if host passed to the client is running"
+ .to_string();
+ }
+ _ => {}
+ }
+ }
+ }
+
+ return format!("{}", e);
+}
+
+/// Check whether a command is blocking.
+pub fn blocking_cmd(cmd: &str) -> bool {
+ cmd.ends_with('b')
+}
+
+/// Chech whether a command is debugging command.
+pub fn debug_format_cmd(cmd: &str) -> bool {
+ cmd.ends_with('?')
+}
+
+/// Check whether the input string is a valid libra address.
+pub fn is_address(data: &str) -> bool {
+ match hex::decode(data) {
+ Ok(vec) => vec.len() == ADDRESS_LENGTH,
+ Err(_) => false,
+ }
+}
+
+/// Returns all the commands available, as well as the reverse index from the aliases to the
+/// commands.
+pub fn get_commands() -> (
+ Vec>,
+ HashMap<&'static str, Arc>,
+) {
+ let commands: Vec> = vec![
+ Arc::new(AccountCommand {}),
+ Arc::new(QueryCommand {}),
+ Arc::new(TransferCommand {}),
+ ];
+ let mut alias_to_cmd = HashMap::new();
+ for command in &commands {
+ for alias in command.get_aliases() {
+ alias_to_cmd.insert(alias, Arc::clone(command));
+ }
+ }
+ (commands, alias_to_cmd)
+}
+
+/// Parse a cmd string, the first element in the returned vector is the command to run
+pub fn parse_cmd(cmd_str: &str) -> Vec<&str> {
+ let input = &cmd_str[..];
+ input.trim().split(' ').map(str::trim).collect()
+}
+
+/// Print the help message for all sub commands.
+pub fn print_subcommand_help(parent_command: &str, commands: &[Box]) {
+ println!(
+ "usage: {} \n\nUse the following args for this command:\n",
+ parent_command
+ );
+ for cmd in commands {
+ println!(
+ "{} {}\n\t{}",
+ cmd.get_aliases().join(" | "),
+ cmd.get_params_help(),
+ cmd.get_description()
+ );
+ }
+ println!("\n");
+}
+
+/// Execute sub command.
+// TODO: Convert subcommands arrays to lazy statics
+pub fn subcommand_execute(
+ parent_command_name: &str,
+ commands: Vec>,
+ client: &mut ClientProxy,
+ params: &[&str],
+) {
+ let mut commands_map = HashMap::new();
+ for (i, cmd) in commands.iter().enumerate() {
+ for alias in cmd.get_aliases() {
+ if commands_map.insert(alias, i) != None {
+ panic!("Duplicate alias {}", alias);
+ }
+ }
+ }
+
+ if params.is_empty() {
+ print_subcommand_help(parent_command_name, &commands);
+ return;
+ }
+
+ match commands_map.get(¶ms[0]) {
+ Some(&idx) => commands[idx].execute(client, ¶ms),
+ _ => print_subcommand_help(parent_command_name, &commands),
+ }
+}
+
+/// Trait to perform client operations.
+pub trait Command {
+ /// all commands and aliases this command support.
+ fn get_aliases(&self) -> Vec<&'static str>;
+ /// string that describes params.
+ fn get_params_help(&self) -> &'static str {
+ ""
+ }
+ /// string that describes whet command does.
+ fn get_description(&self) -> &'static str;
+ /// code to execute.
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]);
+}
diff --git a/client/src/grpc_client.rs b/client/src/grpc_client.rs
new file mode 100644
index 0000000000000..61457ac6622dd
--- /dev/null
+++ b/client/src/grpc_client.rs
@@ -0,0 +1,405 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::AccountData;
+use admission_control_proto::{
+ proto::{
+ admission_control::{
+ SubmitTransactionRequest, SubmitTransactionResponse as ProtoSubmitTransactionResponse,
+ },
+ admission_control_grpc::AdmissionControlClient,
+ },
+ AdmissionControlStatus, SubmitTransactionResponse,
+};
+use failure::prelude::*;
+use futures::Future;
+use grpcio::{CallOption, ChannelBuilder, EnvBuilder};
+use logger::prelude::*;
+use proto_conv::{FromProto, IntoProto};
+use std::sync::Arc;
+use types::{
+ access_path::AccessPath,
+ account_address::AccountAddress,
+ account_config::get_account_resource_or_default,
+ account_state_blob::{AccountStateBlob, AccountStateWithProof},
+ contract_event::{ContractEvent, EventWithProof},
+ get_with_proof::{
+ RequestItem, ResponseItem, UpdateToLatestLedgerRequest, UpdateToLatestLedgerResponse,
+ },
+ transaction::{SignedTransaction, Version},
+ validator_verifier::ValidatorVerifier,
+ vm_error::{VMStatus, VMValidationStatus},
+};
+
+const MAX_GRPC_RETRY_COUNT: u64 = 1;
+
+/// Struct holding dependencies of client.
+pub struct GRPCClient {
+ client: AdmissionControlClient,
+ validator_verifier: Arc,
+}
+
+impl GRPCClient {
+ /// Construct a new Client instance.
+ pub fn new(host: &str, port: &str, validator_verifier: Arc) -> Result {
+ let conn_addr = format!("{}:{}", host, port);
+
+ // Create a GRPC client
+ let env = Arc::new(EnvBuilder::new().name_prefix("grpc-client-").build());
+ let ch = ChannelBuilder::new(env).connect(&conn_addr);
+ let client = AdmissionControlClient::new(ch);
+
+ Ok(GRPCClient {
+ client,
+ validator_verifier,
+ })
+ }
+
+ /// Submits a transaction and bumps the sequence number for the sender
+ pub fn submit_transaction(
+ &self,
+ sender_account: &mut AccountData,
+ req: &SubmitTransactionRequest,
+ ) -> Result<()> {
+ let mut resp = self.submit_transaction_opt(req);
+
+ let mut try_cnt = 0_u64;
+ while Self::need_to_retry(&mut try_cnt, &resp) {
+ resp = self.submit_transaction_opt(&req);
+ }
+
+ let completed_resp = SubmitTransactionResponse::from_proto(resp?)?;
+
+ if let Some(ac_status) = completed_resp.ac_status {
+ if ac_status == AdmissionControlStatus::Accepted {
+ // Bump up sequence_number if transaction is accepted.
+ sender_account.sequence_number += 1;
+ } else {
+ bail!("Transaction failed with AC status: {:?}", ac_status,);
+ }
+ } else if let Some(vm_error) = completed_resp.vm_error {
+ if vm_error == VMStatus::Validation(VMValidationStatus::SequenceNumberTooOld) {
+ sender_account.sequence_number =
+ self.get_sequence_number(sender_account.address)?;
+ bail!(
+ "Transaction failed with vm status: {:?}, please retry your transaction.",
+ vm_error
+ );
+ }
+ bail!("Transaction failed with vm status: {:?}", vm_error);
+ } else if let Some(mempool_error) = completed_resp.mempool_error {
+ bail!(
+ "Transaction failed with mempool status: {:?}",
+ mempool_error,
+ );
+ } else {
+ bail!(
+ "Malformed SubmitTransactionResponse which has no status set, {:?}",
+ completed_resp,
+ );
+ }
+ Ok(())
+ }
+
+ /// Async version of submit_transaction
+ pub fn submit_transaction_async(
+ &self,
+ req: &SubmitTransactionRequest,
+ ) -> Result<(impl Future- )> {
+ let resp = self
+ .client
+ .submit_transaction_async_opt(&req, Self::get_default_grpc_call_option())?
+ .then(|proto_resp| {
+ let ret = SubmitTransactionResponse::from_proto(proto_resp?)?;
+ Ok(ret)
+ });
+ Ok(resp)
+ }
+
+ fn submit_transaction_opt(
+ &self,
+ resp: &SubmitTransactionRequest,
+ ) -> Result
{
+ Ok(self
+ .client
+ .submit_transaction_opt(resp, Self::get_default_grpc_call_option())?)
+ }
+
+ fn get_with_proof_async(
+ &self,
+ requested_items: Vec,
+ ) -> Result> {
+ let req = UpdateToLatestLedgerRequest::new(0, requested_items.clone());
+ debug!("get_with_proof with request: {:?}", req);
+ let proto_req = req.clone().into_proto();
+ let arc_validator_verifier: Arc = Arc::clone(&self.validator_verifier);
+ let ret = self
+ .client
+ .update_to_latest_ledger_async_opt(&proto_req, Self::get_default_grpc_call_option())?
+ .then(move |get_with_proof_resp| {
+ // TODO: Cache/persist client_known_version to work with validator set change when
+ // the feature is available.
+
+ let resp = UpdateToLatestLedgerResponse::from_proto(get_with_proof_resp?)?;
+ resp.verify(arc_validator_verifier, &req)?;
+ Ok(resp)
+ });
+ Ok(ret)
+ }
+
+ fn need_to_retry(try_cnt: &mut u64, ret: &Result) -> bool {
+ if *try_cnt <= MAX_GRPC_RETRY_COUNT {
+ *try_cnt += 1;
+ if let Err(error) = ret {
+ if let Some(grpc_error) = error.downcast_ref::() {
+ if let grpcio::Error::RpcFailure(grpc_rpc_failure) = grpc_error {
+ // Only retry when the connection is down to make sure we won't
+ // send one txn twice.
+ return grpc_rpc_failure.status == grpcio::RpcStatusCode::Unavailable;
+ }
+ }
+ }
+ }
+ false
+ }
+ /// Sync version of get_with_proof
+ pub fn get_with_proof_sync(
+ &self,
+ requested_items: Vec,
+ ) -> Result {
+ let mut resp: Result =
+ self.get_with_proof_async(requested_items.clone())?.wait();
+ let mut try_cnt = 0_u64;
+
+ while Self::need_to_retry(&mut try_cnt, &resp) {
+ resp = self.get_with_proof_async(requested_items.clone())?.wait();
+ }
+
+ Ok(resp?)
+ }
+
+ fn get_balances_async(
+ &self,
+ addresses: &[AccountAddress],
+ ) -> Result, Error = failure::Error>> {
+ let requests = addresses
+ .iter()
+ .map(|addr| RequestItem::GetAccountState { address: *addr })
+ .collect::>();
+
+ let num_addrs = addresses.len();
+ let get_with_proof_resp = self.get_with_proof_async(requests)?;
+ Ok(get_with_proof_resp.then(move |get_with_proof_resp| {
+ let rust_resp = get_with_proof_resp?;
+ if rust_resp.response_items.len() != num_addrs {
+ bail!("Server returned wrong number of responses");
+ }
+
+ let mut balances = vec![];
+ for value_with_proof in rust_resp.response_items {
+ debug!("get_balance response is: {:?}", value_with_proof);
+ match value_with_proof {
+ ResponseItem::GetAccountState {
+ account_state_with_proof,
+ } => {
+ let balance =
+ get_account_resource_or_default(&account_state_with_proof.blob)?
+ .balance();
+ balances.push(balance);
+ }
+ _ => bail!(
+ "Incorrect type of response returned: {:?}",
+ value_with_proof
+ ),
+ }
+ }
+ Ok(balances)
+ }))
+ }
+
+ pub(crate) fn get_balance(&self, address: AccountAddress) -> Result {
+ let mut ret = self.get_balances_async(&[address])?.wait();
+ let mut try_cnt = 0_u64;
+ while Self::need_to_retry(&mut try_cnt, &ret) {
+ ret = self.get_balances_async(&[address])?.wait();
+ }
+
+ ret?.pop()
+ .ok_or_else(|| format_err!("Account is not available!"))
+ }
+
+ /// Get the latest account sequence number for the account specified.
+ pub fn get_sequence_number(&self, address: AccountAddress) -> Result {
+ Ok(get_account_resource_or_default(&self.get_account_blob(address)?.0)?.sequence_number())
+ }
+
+ /// Get the latest account state blob from validator.
+ pub fn get_account_blob(
+ &self,
+ address: AccountAddress,
+ ) -> Result<(Option, Version)> {
+ let req_item = RequestItem::GetAccountState { address };
+
+ let mut response = self.get_with_proof_sync(vec![req_item])?;
+ let account_state_with_proof = response
+ .response_items
+ .remove(0)
+ .into_get_account_state_response()?;
+
+ Ok((
+ account_state_with_proof.blob,
+ response.ledger_info_with_sigs.ledger_info().version(),
+ ))
+ }
+
+ /// Get transaction from validator by account and sequence number.
+ pub fn get_txn_by_acc_seq(
+ &self,
+ account: AccountAddress,
+ sequence_number: u64,
+ fetch_events: bool,
+ ) -> Result>)>> {
+ let req_item = RequestItem::GetAccountTransactionBySequenceNumber {
+ account,
+ sequence_number,
+ fetch_events,
+ };
+
+ let mut response = self.get_with_proof_sync(vec![req_item])?;
+ let (signed_txn_with_proof, _) = response
+ .response_items
+ .remove(0)
+ .into_get_account_txn_by_seq_num_response()?;
+
+ Ok(signed_txn_with_proof.map(|t| (t.signed_transaction, t.events)))
+ }
+
+ /// Get transactions in range (start_version..start_version + limit - 1) from validator.
+ pub fn get_txn_by_range(
+ &self,
+ start_version: u64,
+ limit: u64,
+ fetch_events: bool,
+ ) -> Result>)>> {
+ // Make the request.
+ let req_item = RequestItem::GetTransactions {
+ start_version,
+ limit,
+ fetch_events,
+ };
+ let mut response = self.get_with_proof_sync(vec![req_item])?;
+ let txn_list_with_proof = response
+ .response_items
+ .remove(0)
+ .into_get_transactions_response()?;
+
+ // Transform the response.
+ let num_txns = txn_list_with_proof.transaction_and_infos.len();
+ let event_lists = txn_list_with_proof
+ .events
+ .map(|event_lists| event_lists.into_iter().map(Some).collect())
+ .unwrap_or_else(|| vec![None; num_txns]);
+
+ let res = itertools::zip_eq(txn_list_with_proof.transaction_and_infos, event_lists)
+ .map(|((signed_txn, _), events)| (signed_txn, events))
+ .collect();
+ Ok(res)
+ }
+
+ /// Get event by access path from validator. AccountStateWithProof will be returned if
+ /// 1. No event is available. 2. Ascending and available event number < limit.
+ /// 3. Descending and start_seq_num > latest account event sequence number.
+ pub fn get_events_by_access_path(
+ &self,
+ access_path: AccessPath,
+ start_event_seq_num: u64,
+ ascending: bool,
+ limit: u64,
+ ) -> Result<(Vec, Option)> {
+ let req_item = RequestItem::GetEventsByEventAccessPath {
+ access_path,
+ start_event_seq_num,
+ ascending,
+ limit,
+ };
+
+ let mut response = self.get_with_proof_sync(vec![req_item])?;
+ let value_with_proof = response.response_items.remove(0);
+ match value_with_proof {
+ ResponseItem::GetEventsByEventAccessPath {
+ events_with_proof,
+ proof_of_latest_event,
+ } => Ok((events_with_proof, proof_of_latest_event)),
+ _ => bail!(
+ "Incorrect type of response returned: {:?}",
+ value_with_proof
+ ),
+ }
+ }
+
+ fn get_default_grpc_call_option() -> CallOption {
+ CallOption::default()
+ .wait_for_ready(true)
+ .timeout(std::time::Duration::from_millis(5000))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::client_proxy::{AddressAndIndex, ClientProxy};
+ use config::trusted_peers::TrustedPeersConfigHelpers;
+ use libra_wallet::io_utils;
+ use tempfile::NamedTempFile;
+
+ pub fn generate_accounts_from_wallet(count: usize) -> (ClientProxy, Vec) {
+ let mut accounts = Vec::new();
+ accounts.reserve(count);
+ let file = NamedTempFile::new().unwrap();
+ let mnemonic_path = file.into_temp_path().to_str().unwrap().to_string();
+ let trust_peer_file = NamedTempFile::new().unwrap();
+ let (_, trust_peer_config) = TrustedPeersConfigHelpers::get_test_config(1, None);
+ let trust_peer_path = trust_peer_file.into_temp_path();
+ trust_peer_config.save_config(&trust_peer_path);
+
+ let val_set_file = trust_peer_path.to_str().unwrap().to_string();
+
+ // We don't need to specify host/port since the client won't be used to connect, only to
+ // generate random accounts
+ let mut client_proxy = ClientProxy::new(
+ "", /* host */
+ "", /* port */
+ &val_set_file,
+ &"",
+ None,
+ Some(mnemonic_path),
+ )
+ .unwrap();
+ for _ in 0..count {
+ accounts.push(client_proxy.create_next_account(&["c"]).unwrap());
+ }
+
+ (client_proxy, accounts)
+ }
+
+ #[test]
+ fn test_generate() {
+ let num = 1;
+ let (_, accounts) = generate_accounts_from_wallet(num);
+ assert_eq!(accounts.len(), num);
+ }
+
+ #[test]
+ fn test_write_recover() {
+ let num = 100;
+ let (client, accounts) = generate_accounts_from_wallet(num);
+ assert_eq!(accounts.len(), num);
+
+ let file = NamedTempFile::new().unwrap();
+ let path = file.into_temp_path();
+ io_utils::write_recovery(&client.wallet, &path).expect("failed to write to file");
+
+ let wallet = io_utils::recover(&path).expect("failed to load from file");
+
+ assert_eq!(client.wallet.mnemonic(), wallet.mnemonic());
+ }
+}
diff --git a/client/src/lib.rs b/client/src/lib.rs
new file mode 100644
index 0000000000000..6e5660d46f40a
--- /dev/null
+++ b/client/src/lib.rs
@@ -0,0 +1,47 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+#![feature(duration_float)]
+#![deny(missing_docs)]
+//! Libra Client
+//!
+//! Client (binary) is the CLI tool to interact with Libra validator.
+//! It supposes all public APIs.
+use crypto::signing::KeyPair;
+use serde::{Deserialize, Serialize};
+use types::account_address::AccountAddress;
+
+pub(crate) mod account_commands;
+/// Main instance of client holding corresponding information, e.g. account address.
+pub mod client_proxy;
+/// Command struct to interact with client.
+pub mod commands;
+/// gRPC client wrapper to connect to validator.
+pub(crate) mod grpc_client;
+pub(crate) mod query_commands;
+pub(crate) mod transfer_commands;
+
+/// Struct used to store data for each created account. We track the sequence number
+/// so we can create new transactions easily
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct AccountData {
+ /// Address of the account.
+ pub address: AccountAddress,
+ /// (private_key, public_key) pair if the account is not managed by wallet
+ pub key_pair: Option,
+ /// Latest sequence number maintained by client, it can be different from validator.
+ pub sequence_number: u64,
+}
+
+impl AccountData {
+ /// Serialize account keypair if exists.
+ pub fn keypair_as_string(&self) -> Option<(String, String)> {
+ match &self.key_pair {
+ Some(key_pair) => Some((
+ crypto::utils::encode_to_string(&key_pair.private_key()),
+ crypto::utils::encode_to_string(&key_pair.public_key()),
+ )),
+ None => None,
+ }
+ }
+}
diff --git a/client/src/main.rs b/client/src/main.rs
new file mode 100644
index 0000000000000..6ff92c985264c
--- /dev/null
+++ b/client/src/main.rs
@@ -0,0 +1,137 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use client::{client_proxy::ClientProxy, commands::*};
+use logger::set_default_global_logger;
+use rustyline::{config::CompletionType, error::ReadlineError, Config, Editor};
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "Libra Client",
+ author = "The Libra Association",
+ about = "Libra client to connect to a specific validator"
+)]
+struct Args {
+ /// Admission Control port to connect to.
+ #[structopt(short = "p", long = "port", default_value = "30307")]
+ pub port: String,
+ /// Host address/name to connect to.
+ #[structopt(short = "a", long = "host")]
+ pub host: String,
+ /// Path to the generated keypair for the faucet account. The faucet account can be used to
+ /// mint coins. If not passed, a new keypair will be generated for
+ /// you and placed in a temporary directory.
+ /// To manually generate a keypair, use generate_keypair:
+ /// `cargo run -p generate_keypair -- -o `
+ #[structopt(short = "m", long = "faucet_key_file_path")]
+ pub faucet_account_file: Option,
+ /// Host that operates a faucet service
+ /// If not passed, will be derived from host parameter
+ #[structopt(short = "f", long = "faucet_server")]
+ pub faucet_server: Option,
+ /// File location from which to load mnemonic word for user account address/key generation.
+ /// If not passed, a new mnemonic file will be generated by libra_wallet in the current
+ /// directory.
+ #[structopt(short = "n", long = "mnemonic_file")]
+ pub mnemonic_file: Option,
+ /// File location from which to load config of trusted validators. It is used to verify
+ /// validator signatures in validator query response. The file should at least include public
+ /// key of all validators trusted by the client - which should typically be all validators on
+ /// the network. To connect to testnet, use 'libra/scripts/cli/trusted_peers.config.toml'.
+ /// Can be generated by libra-config for local testing:
+ /// `cargo run --bin libra-config`
+ /// But the preferred method is to simply use libra-swarm to run local networks
+ #[structopt(short = "s", long = "validator_set_file")]
+ pub validator_set_file: String,
+}
+
+fn main() -> std::io::Result<()> {
+ let _logger = set_default_global_logger(false /* async */, None);
+ crash_handler::setup_panic_handler();
+
+ let (commands, alias_to_cmd) = get_commands();
+
+ let args = Args::from_args();
+ let faucet_account_file = args.faucet_account_file.unwrap_or_else(|| "".to_string());
+
+ let mut client_proxy = ClientProxy::new(
+ &args.host,
+ &args.port,
+ &args.validator_set_file,
+ &faucet_account_file,
+ args.faucet_server,
+ args.mnemonic_file,
+ )
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, &format!("{}", e)[..]))?;
+
+ // Test connection to validator
+ let test_ret = client_proxy.test_validator_connection();
+
+ if let Err(e) = test_ret {
+ println!(
+ "Not able to connect to validator at {}:{}, error {:?}",
+ args.host, args.port, e
+ );
+ return Ok(());
+ }
+ let cli_info = format!("Connected to validator at: {}:{}", args.host, args.port);
+ print_help(&cli_info, &commands);
+ println!("Please, input commands: \n");
+
+ let config = Config::builder()
+ .history_ignore_space(true)
+ .completion_type(CompletionType::List)
+ .auto_add_history(true)
+ .build();
+ let mut rl = Editor::<()>::with_config(config);
+ loop {
+ let readline = rl.readline("libra% ");
+ match readline {
+ Ok(line) => {
+ let params = parse_cmd(&line);
+ match alias_to_cmd.get(params[0]) {
+ Some(cmd) => cmd.execute(&mut client_proxy, ¶ms),
+ None => match params[0] {
+ "quit" | "q!" => break,
+ "help" | "h" => print_help(&cli_info, &commands),
+ "" => continue,
+ x => println!("Unknown command: {:?}", x),
+ },
+ }
+ }
+ Err(ReadlineError::Interrupted) => {
+ println!("CTRL-C");
+ break;
+ }
+ Err(ReadlineError::Eof) => {
+ println!("CTRL-D");
+ break;
+ }
+ Err(err) => {
+ println!("Error: {:?}", err);
+ break;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+/// Print the help message for the client and underlying command.
+fn print_help(client_info: &str, commands: &[std::sync::Arc]) {
+ println!("{}", client_info);
+ println!("usage: \n\nUse the following commands:\n");
+ for cmd in commands {
+ println!(
+ "{} {}\n\t{}",
+ cmd.get_aliases().join(" | "),
+ cmd.get_params_help(),
+ cmd.get_description()
+ );
+ }
+
+ println!("help | h \n\tPrints this help");
+ println!("quit | q! \n\tExit this client");
+ println!("\n");
+}
diff --git a/client/src/query_commands.rs b/client/src/query_commands.rs
new file mode 100644
index 0000000000000..454572d9247d7
--- /dev/null
+++ b/client/src/query_commands.rs
@@ -0,0 +1,231 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{client_proxy::ClientProxy, commands::*};
+use types::account_config::get_account_resource_or_default;
+use vm_genesis::get_transaction_name;
+
+/// Major command for query operations.
+pub struct QueryCommand {}
+
+impl Command for QueryCommand {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["query", "q"]
+ }
+ fn get_description(&self) -> &'static str {
+ "Query operations"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ let commands: Vec> = vec![
+ Box::new(QueryCommandGetBalance {}),
+ Box::new(QueryCommandGetSeqNum {}),
+ Box::new(QueryCommandGetLatestAccountState {}),
+ Box::new(QueryCommandGetTxnByAccountSeq {}),
+ Box::new(QueryCommandGetTxnByRange {}),
+ Box::new(QueryCommandGetEvent {}),
+ ];
+
+ subcommand_execute(¶ms[0], commands, client, ¶ms[1..]);
+ }
+}
+
+/// Sub commands to query balance for the account specified.
+pub struct QueryCommandGetBalance {}
+
+impl Command for QueryCommandGetBalance {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["balance", "b"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "|"
+ }
+ fn get_description(&self) -> &'static str {
+ "Get the current balance of an account"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ if params.len() != 2 {
+ println!("Invalid number of arguments for balance query");
+ return;
+ }
+ match client.get_balance(¶ms) {
+ Ok(balance) => println!("Balance is: {}", balance),
+ Err(e) => report_error("Failed to get balance", e),
+ }
+ }
+}
+
+/// Sub command to get the latest sequence number from validator for the account specified.
+pub struct QueryCommandGetSeqNum {}
+
+impl Command for QueryCommandGetSeqNum {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["sequence", "s"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "| [reset_sequence_number=true|false]"
+ }
+ fn get_description(&self) -> &'static str {
+ "Get the current sequence number for an account, \
+ and reset current sequence number in CLI (optional, default is false)"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Getting current sequence number");
+ match client.get_sequence_number(¶ms) {
+ Ok(sn) => println!("Sequence number is: {}", sn),
+ Err(e) => report_error("Error getting sequence number", e),
+ }
+ }
+}
+
+/// Command to query latest account state from validator.
+pub struct QueryCommandGetLatestAccountState {}
+
+impl Command for QueryCommandGetLatestAccountState {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["account_state", "as"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "|"
+ }
+ fn get_description(&self) -> &'static str {
+ "Get the latest state for an account"
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Getting latest account state");
+ match client.get_latest_account_state(¶ms) {
+ Ok((acc, version)) => match get_account_resource_or_default(&acc) {
+ Ok(_) => println!(
+ "Latest account state is: \n \
+ Account: {:#?}\n \
+ State: {:#?}\n \
+ Blockchain Version: {}\n",
+ client
+ .get_account_address_from_parameter(params[1])
+ .expect("Unable to parse account parameter"),
+ acc,
+ version,
+ ),
+ Err(e) => report_error("Error converting account blob to account resource", e),
+ },
+ Err(e) => report_error("Error getting latest account state", e),
+ }
+ }
+}
+
+/// Sub command to get transaction by account and sequence number from validator.
+pub struct QueryCommandGetTxnByAccountSeq {}
+
+impl Command for QueryCommandGetTxnByAccountSeq {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["txn_acc_seq", "ts"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "| "
+ }
+ fn get_description(&self) -> &'static str {
+ "Get the committed transaction by account and sequence number. \
+ Optionally also fetch events emitted by this transaction."
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Getting committed transaction by account and sequence number");
+ match client.get_committed_txn_by_acc_seq(¶ms) {
+ Ok(txn_and_events) => {
+ match txn_and_events {
+ Some((comm_txn, events)) => {
+ println!(
+ "Committed transaction: {}",
+ comm_txn.format_for_client(get_transaction_name)
+ );
+ if let Some(events_inner) = &events {
+ println!("Events: ");
+ for event in events_inner {
+ println!("{}", event);
+ }
+ }
+ }
+ None => println!("Transaction not available"),
+ };
+ }
+ Err(e) => report_error(
+ "Error getting committed transaction by account and sequence number",
+ e,
+ ),
+ }
+ }
+}
+
+/// Sub command to query transactions by range from validator.
+pub struct QueryCommandGetTxnByRange {}
+
+impl Command for QueryCommandGetTxnByRange {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["txn_range", "tr"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ " "
+ }
+ fn get_description(&self) -> &'static str {
+ "Get the committed transactions by version range. \
+ Optionally also fetch events emitted by these transactions."
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Getting committed transaction by range");
+ match client.get_committed_txn_by_range(¶ms) {
+ Ok(comm_txns_and_events) => {
+ // Note that this should never panic because we shouldn't return items
+ // if the version wasn't able to be parsed in the first place
+ let mut cur_version = params[1].parse::().expect("Unable to parse version");
+ for (txn, opt_events) in comm_txns_and_events {
+ println!(
+ "Transaction at version {}: {}",
+ cur_version,
+ txn.format_for_client(get_transaction_name)
+ );
+ if opt_events.is_some() {
+ let events = opt_events.unwrap();
+ if events.is_empty() {
+ println!("No events returned");
+ } else {
+ for event in events {
+ println!("{}", event);
+ }
+ }
+ }
+ cur_version += 1;
+ }
+ }
+ Err(e) => report_error("Error getting committed transactions by range", e),
+ }
+ }
+}
+
+/// Sub command to query events from validator.
+pub struct QueryCommandGetEvent {}
+
+impl Command for QueryCommandGetEvent {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["event", "ev"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "| "
+ }
+ fn get_description(&self) -> &'static str {
+ "Get events by account and event type (sent|received)."
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ println!(">> Getting events by account and event type.");
+ match client.get_events_by_account_and_type(¶ms) {
+ Ok((events, last_event_state)) => {
+ if events.is_empty() {
+ println!("No events returned");
+ } else {
+ for event in events {
+ println!("{}", event);
+ }
+ }
+ println!("Last event state: {:#?}", last_event_state);
+ }
+ Err(e) => report_error("Error getting events by access path", e),
+ }
+ }
+}
diff --git a/client/src/transfer_commands.rs b/client/src/transfer_commands.rs
new file mode 100644
index 0000000000000..21c4ece2dc407
--- /dev/null
+++ b/client/src/transfer_commands.rs
@@ -0,0 +1,51 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{client_proxy::ClientProxy, commands::*};
+
+/// Command to transfer coins between two accounts.
+pub struct TransferCommand {}
+
+impl Command for TransferCommand {
+ fn get_aliases(&self) -> Vec<&'static str> {
+ vec!["transfer", "transferb", "t", "tb"]
+ }
+ fn get_params_help(&self) -> &'static str {
+ "\n\t| \
+ | \
+ [gas_unit_price (default=0)] [max_gas_amount (default 10000)] \
+ Suffix 'b' is for blocking. "
+ }
+ fn get_description(&self) -> &'static str {
+ "Transfer coins from account to another."
+ }
+ fn execute(&self, client: &mut ClientProxy, params: &[&str]) {
+ if params.len() < 4 || params.len() > 6 {
+ println!("Invalid number of arguments for transfer");
+ println!(
+ "{} {}",
+ self.get_aliases().join(" | "),
+ self.get_params_help()
+ );
+ return;
+ }
+
+ println!(">> Transferring");
+ let is_blocking = blocking_cmd(¶ms[0]);
+ match client.transfer_coins(¶ms, is_blocking) {
+ Ok(index_and_seq) => {
+ if is_blocking {
+ println!("Finished transaction!");
+ } else {
+ println!("Transaction submitted to validator");
+ }
+ println!(
+ "To query for transaction status, run: query txn_acc_seq {} {} \
+ ",
+ index_and_seq.account_index, index_and_seq.sequence_number
+ );
+ }
+ Err(e) => report_error("Failed to perform transaction", e),
+ }
+ }
+}
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000000000..a1e1f754f92f4
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1,6 @@
+# cyclomatic complexity is not always useful
+cognitive-complexity-threshold = 100
+# types are used for safety encoding
+type-complexity-threshold = 10000
+# manipulating complex states machines in consensus
+too-many-arguments-threshold = 15
diff --git a/common/build_helpers/Cargo.toml b/common/build_helpers/Cargo.toml
new file mode 100644
index 0000000000000..7731cce35a643
--- /dev/null
+++ b/common/build_helpers/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "build_helpers"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies]
+protoc-grpcio = "0.3.1"
+walkdir = "2.2.0"
+
+grpcio-client = {path = "../grpcio-client"}
diff --git a/common/build_helpers/src/build_helpers.rs b/common/build_helpers/src/build_helpers.rs
new file mode 100644
index 0000000000000..1f1efa438ca80
--- /dev/null
+++ b/common/build_helpers/src/build_helpers.rs
@@ -0,0 +1,81 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+/// Contains helpers for build.rs files. Includes helpers for proto compilation
+use std::path::{Path, PathBuf};
+
+use walkdir::WalkDir;
+
+// Compiles all proto files under proto root and dependent roots.
+// For example, if there is a file `src/a/b/c.proto`, it will generate `src/a/b/c.rs` and
+// `src/a/b/c_grpc.rs`.
+pub fn compile_proto(proto_root: &str, dependent_roots: Vec<&str>, generate_client_code: bool) {
+ let mut additional_includes = vec![];
+ for dependent_root in dependent_roots {
+ // First compile dependent directories
+ compile_dir(
+ &dependent_root,
+ vec![], /* additional_includes */
+ false, /* generate_client_code */
+ );
+ additional_includes.push(Path::new(dependent_root).to_path_buf());
+ }
+ // Now compile this directory
+ compile_dir(&proto_root, additional_includes, generate_client_code);
+}
+
+// Compile all of the proto files in proto_root directory and use the additional
+// includes when compiling.
+pub fn compile_dir(
+ proto_root: &str,
+ additional_includes: Vec,
+ generate_client_code: bool,
+) {
+ for entry in WalkDir::new(proto_root) {
+ let p = entry.unwrap();
+ if p.file_type().is_dir() {
+ continue;
+ }
+
+ let path = p.path();
+ if let Some(ext) = path.extension() {
+ if ext != "proto" {
+ continue;
+ }
+ println!("cargo:rerun-if-changed={}", path.display());
+ compile(&path, &additional_includes, generate_client_code);
+ }
+ }
+}
+
+fn compile(path: &Path, additional_includes: &[PathBuf], generate_client_code: bool) {
+ let parent = path.parent().unwrap();
+ let mut src_path = parent.to_owned().to_path_buf();
+ src_path.push("src");
+
+ let mut includes = additional_includes.to_owned();
+ includes.push(parent.to_path_buf());
+
+ ::protoc_grpcio::compile_grpc_protos(&[path], includes.as_slice(), parent)
+ .unwrap_or_else(|_| panic!("Failed to compile protobuf input: {:?}", path));
+
+ if generate_client_code {
+ let file_string = path
+ .file_name()
+ .expect("unable to get filename")
+ .to_str()
+ .unwrap();
+ let includes_strings = includes
+ .iter()
+ .map(|x| x.to_str().unwrap())
+ .collect::>();
+
+ // generate client code
+ grpcio_client::client_stub_gen(
+ &[file_string],
+ includes_strings.as_slice(),
+ &parent.to_str().unwrap(),
+ )
+ .expect("Unable to generate client stub");
+ }
+}
diff --git a/common/build_helpers/src/lib.rs b/common/build_helpers/src/lib.rs
new file mode 100644
index 0000000000000..454e5fa1739ea
--- /dev/null
+++ b/common/build_helpers/src/lib.rs
@@ -0,0 +1,4 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+pub mod build_helpers;
diff --git a/common/canonical_serialization/Cargo.toml b/common/canonical_serialization/Cargo.toml
new file mode 100644
index 0000000000000..28b85c818b18a
--- /dev/null
+++ b/common/canonical_serialization/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "canonical_serialization"
+version = "0.1.0"
+authors = ["Libra Association "]
+license = "Apache-2.0"
+publish = false
+edition = "2018"
+
+[dependencies]
+byteorder = "1.3.1"
+
+failure = { path = "../failure_ext", package = "failure_ext" }
+
+[dev-dependencies]
+hex = "0.3"
diff --git a/common/canonical_serialization/src/canonical_serialization_test.rs b/common/canonical_serialization/src/canonical_serialization_test.rs
new file mode 100644
index 0000000000000..c44a830f458d0
--- /dev/null
+++ b/common/canonical_serialization/src/canonical_serialization_test.rs
@@ -0,0 +1,321 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
+//disable it in test so that we can use variable names such as 'foo' and 'bar'
+#![allow(clippy::blacklisted_name)]
+#![allow(clippy::many_single_char_names)]
+
+use super::*;
+use byteorder::WriteBytesExt;
+use failure::Result;
+use std::u32;
+
+// Do not change the test vectors. Please read the comment below.
+const TEST_VECTOR_1: &str = "ffffffffffffffff060000006463584d4237640000000000000009000000000102\
+ 03040506070805050505050505050505050505050505050505050505050505050505\
+ 05050505630000000103000000010000000103000000161543030000000038150300\
+ 0000160a05040000001415596903000000c9175a";
+
+// Why do we need test vectors?
+//
+// 1. Sometimes it help to catch common bugs between serialization and
+// deserialization functions that would have been missed by a simple round trip test.
+// For example, if there's a bug in a shared procedure that serialize and
+// deserialize both calls then roundtrip might miss it.
+//
+// 2. It helps to catch code changes that inadvertently introduce breaking changes
+// in the serialization format that is incompatible with what generated in the
+// past which would be missed by roundtrip tests, or changes that are not backward
+// compatible in the sense that it may fail to deserialize bytes generated in the past.
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Addr(pub [u8; 32]);
+
+impl Addr {
+ fn new(bytes: [u8; 32]) -> Self {
+ Addr(bytes)
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct Foo {
+ a: u64,
+ b: Vec,
+ c: Bar,
+ d: bool,
+ e: BTreeMap, Vec>,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct Bar {
+ a: u64,
+ b: Vec,
+ c: Addr,
+ d: u32,
+}
+
+impl CanonicalSerialize for Foo {
+ fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
+ serializer
+ .encode_u64(self.a)?
+ .encode_variable_length_bytes(&self.b)?
+ .encode_struct(&self.c)?
+ .encode_bool(self.d)?
+ .encode_btreemap(&self.e)?;
+ Ok(())
+ }
+}
+
+impl CanonicalSerialize for Bar {
+ fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
+ serializer
+ .encode_u64(self.a)?
+ .encode_variable_length_bytes(&self.b)?
+ .encode_raw_bytes(&self.c.0)?
+ .encode_u32(self.d)?;
+ Ok(())
+ }
+}
+
+impl CanonicalDeserialize for Foo {
+ fn deserialize(deserializer: &mut impl CanonicalDeserializer) -> Result {
+ let a = deserializer.decode_u64()?;
+ let b = deserializer.decode_variable_length_bytes()?;
+ let c: Bar = deserializer.decode_struct::()?;
+ let d: bool = deserializer.decode_bool()?;
+ let e: BTreeMap, Vec> = deserializer.decode_btreemap()?;
+ Ok(Foo { a, b, c, d, e })
+ }
+}
+
+impl CanonicalDeserialize for Bar {
+ fn deserialize(deserializer: &mut impl CanonicalDeserializer) -> Result {
+ let a = deserializer.decode_u64()?;
+ let b = deserializer.decode_variable_length_bytes()?;
+ let c = deserializer.decode_bytes_with_len(32)?;
+ let mut cc: [u8; 32] = [0; 32];
+ cc.copy_from_slice(c.as_slice());
+
+ let d = deserializer.decode_u32()?;
+ Ok(Bar {
+ a,
+ b,
+ c: Addr::new(cc),
+ d,
+ })
+ }
+}
+
+#[test]
+fn test_btreemap_encode() {
+ let mut map = BTreeMap::new();
+ let value = vec![54, 20, 21, 200];
+ let key1 = vec![0]; // after serialization: [1, 0]
+ let key2 = vec![0, 6]; // after serialization: [2, 0, 6]
+ let key3 = vec![1]; // after serialization: [1, 1]
+ let key4 = vec![2]; // after serialization: [1, 2]
+ map.insert(key1.clone(), value.clone());
+ map.insert(key2.clone(), value.clone());
+ map.insert(key3.clone(), value.clone());
+ map.insert(key4.clone(), value.clone());
+
+ let serialized_bytes = SimpleSerializer::>::serialize(&map).unwrap();
+
+ let mut deserializer = SimpleDeserializer::new(&serialized_bytes);
+
+ // ensure the order was encoded in lexicographic order
+ assert_eq!(deserializer.raw_bytes.read_u32::().unwrap(), 4);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), key1);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), value);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), key3);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), value);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), key4);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), value);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), key2);
+ assert_eq!(deserializer.decode_variable_length_bytes().unwrap(), value);
+}
+
+#[test]
+fn test_serialization_roundtrip() {
+ let bar = Bar {
+ a: 50,
+ b: vec![10u8; 100],
+ c: Addr::new([3u8; 32]),
+ d: 12,
+ };
+
+ let mut map = BTreeMap::new();
+ map.insert(vec![0, 56, 21], vec![22, 10, 5]);
+ map.insert(vec![1], vec![22, 21, 67]);
+ map.insert(vec![20, 21, 89, 105], vec![201, 23, 90]);
+
+ let foo = Foo {
+ a: 1,
+ b: vec![32, 41, 190, 200, 2, 5, 90, 100, 123, 234, 159, 159, 101],
+ c: bar,
+ d: false,
+ e: map,
+ };
+
+ let mut serializer = SimpleSerializer::>::new();
+ foo.serialize(&mut serializer).unwrap();
+ let serialized_bytes = serializer.get_output();
+
+ let mut deserializer = SimpleDeserializer::new(&serialized_bytes);
+ let deserialized_foo = Foo::deserialize(&mut deserializer).unwrap();
+ assert_eq!(foo, deserialized_foo);
+ assert_eq!(
+ deserializer.raw_bytes.position(),
+ deserializer.raw_bytes.get_ref().len() as u64
+ );
+}
+
+#[test]
+fn test_encode_vec() {
+ let bar1 = Bar {
+ a: 55,
+ b: vec![10u8; 100],
+ c: Addr::new([3u8; 32]),
+ d: 77,
+ };
+ let bar2 = Bar {
+ a: 123,
+ b: vec![1, 5, 20],
+ c: Addr::new([8u8; 32]),
+ d: 127,
+ };
+
+ let mut vec = Vec::new();
+ vec.push(bar1.clone());
+ vec.push(bar2.clone());
+ let mut serializer = SimpleSerializer::>::new();
+ serializer.encode_vec(&vec).unwrap();
+ let serialized_bytes = serializer.get_output();
+
+ let de_vec: Vec = SimpleDeserializer::deserialize(&serialized_bytes).unwrap();
+
+ assert_eq!(2, de_vec.len());
+ assert_eq!(bar1, de_vec[0]);
+ assert_eq!(bar2, de_vec[1]);
+
+ // test Vec implementation
+ let mut serializer = SimpleSerializer::>::new();
+ serializer.encode_struct(&vec).unwrap();
+ let serialized_bytes = serializer.get_output();
+ let de_vec: Vec = SimpleDeserializer::deserialize(&serialized_bytes).unwrap();
+
+ assert_eq!(2, de_vec.len());
+ assert_eq!(bar1, de_vec[0]);
+ assert_eq!(bar2, de_vec[1]);
+}
+
+#[test]
+fn test_vec_impl() {
+ let mut vec: Vec = Vec::new();
+ vec.push(std::i32::MIN);
+ vec.push(std::i32::MAX);
+ vec.push(100);
+
+ let mut serializer = SimpleSerializer::>::new();
+ serializer.encode_struct(&vec).unwrap();
+ let serialized_bytes = serializer.get_output();
+ let de_vec: Vec = SimpleDeserializer::deserialize(&serialized_bytes).unwrap();
+ assert_eq!(vec, de_vec);
+}
+
+#[test]
+fn test_vectors_1() {
+ let bar = Bar {
+ a: 100,
+ b: vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
+ c: Addr::new([5u8; 32]),
+ d: 99,
+ };
+
+ let mut map = BTreeMap::new();
+ map.insert(vec![0, 56, 21], vec![22, 10, 5]);
+ map.insert(vec![1], vec![22, 21, 67]);
+ map.insert(vec![20, 21, 89, 105], vec![201, 23, 90]);
+
+ let foo = Foo {
+ a: u64::max_value(),
+ b: vec![100, 99, 88, 77, 66, 55],
+ c: bar,
+ d: true,
+ e: map,
+ };
+
+ let mut serializer = SimpleSerializer::>::new();
+ foo.serialize(&mut serializer).unwrap();
+ let serialized_bytes = serializer.get_output();
+
+ // make sure we serialize into exact same bytes as before
+ assert_eq!(TEST_VECTOR_1, hex::encode(serialized_bytes));
+
+ // make sure we can deserialize the test vector into expected struct
+ let test_vector_bytes = hex::decode(TEST_VECTOR_1).unwrap();
+ let deserialized_foo: Foo = SimpleDeserializer::deserialize(&test_vector_bytes).unwrap();
+ assert_eq!(foo, deserialized_foo);
+}
+
+#[test]
+fn test_serialization_failure_cases() {
+ // a vec longer than representable range should result in failure
+ let bar = Bar {
+ a: 100,
+ b: vec![0; i32::max_value() as usize + 1],
+ c: Addr::new([0u8; 32]),
+ d: 222,
+ };
+
+ let mut serializer = SimpleSerializer::>::new();
+ assert!(bar.serialize(&mut serializer).is_err());
+}
+
+#[test]
+fn test_deserialization_failure_cases() {
+ // invalid length prefix should fail on all decoding methods
+ let bytes_len_2 = vec![0; 2];
+ let mut deserializer = SimpleDeserializer::new(&bytes_len_2);
+ assert!(deserializer.clone().decode_u64().is_err());
+ assert!(deserializer.clone().decode_bytes_with_len(32).is_err());
+ assert!(deserializer.clone().decode_variable_length_bytes().is_err());
+ assert!(deserializer.clone().decode_struct::().is_err());
+ assert!(Foo::deserialize(&mut deserializer.clone()).is_err());
+
+ // a length prefix longer than maximum allowed should fail
+ let mut long_bytes = Vec::new();
+ long_bytes
+ .write_u32::(ARRAY_MAX_LENGTH as u32 + 1)
+ .unwrap();
+ deserializer = SimpleDeserializer::new(&long_bytes);
+ assert!(deserializer.clone().decode_variable_length_bytes().is_err());
+
+ // vec not long enough should fail
+ let mut bytes_len_10 = Vec::new();
+ bytes_len_10.write_u32::(32).unwrap();
+ deserializer = SimpleDeserializer::new(&bytes_len_10);
+ assert!(deserializer.clone().decode_variable_length_bytes().is_err());
+ assert!(deserializer.clone().decode_bytes_with_len(32).is_err());
+
+ // malformed struct should fail
+ let mut some_bytes = Vec::new();
+ some_bytes.write_u64::(10).unwrap();
+ some_bytes.write_u32::(50).unwrap();
+ deserializer = SimpleDeserializer::new(&some_bytes);
+ assert!(deserializer.clone().decode_struct::().is_err());
+
+ // malformed encoded bytes with length prefix larger than real
+ let mut evil_bytes = Vec::new();
+ evil_bytes.write_u32::(500).unwrap();
+ evil_bytes.resize_with(4 + 499, Default::default);
+ deserializer = SimpleDeserializer::new(&evil_bytes);
+ assert!(deserializer.clone().decode_variable_length_bytes().is_err());
+
+ // malformed encoded bool with value not 0 or 1
+ let mut bool_bytes = Vec::new();
+ bool_bytes.write_u8(2).unwrap();
+ deserializer = SimpleDeserializer::new(&bool_bytes);
+ assert!(deserializer.clone().decode_bool().is_err());
+}
diff --git a/common/canonical_serialization/src/lib.rs b/common/canonical_serialization/src/lib.rs
new file mode 100644
index 0000000000000..612e3471e802d
--- /dev/null
+++ b/common/canonical_serialization/src/lib.rs
@@ -0,0 +1,535 @@
+// Copyright (c) The Libra Core Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+//! This module defines traits and implementations of canonical serialization mechanism.
+//!
+//! A struct can implement the CanonicalSerialize trait to specify how to serialize itself,
+//! and the CanonicalDeserialize trait to specify deserialization, if it needs to. One design
+//! goal of this serialization format is to optimize for simplicity. It is not designed to be
+//! another full-fledged network serialization as Protobuf or Thrift. It is designed
+//! for doing only one thing right, which is to deterministically generate consistent bytes
+//! from a data structure.
+//!
+//! A good example of how to use this framework is described in
+//! ./canonical_serialization_test.rs
+//!
+//! An extremely simple implementation of CanonicalSerializer is also provided, the encoding
+//! rules are:
+//! (All unsigned integers are encoded in little endian representation unless specified otherwise)
+//!
+//! 1. The encoding of an unsigned 64-bit integer is defined as its little endian representation
+//! in 8 bytes
+//!
+//! 2. The encoding of an item (byte array) is defined as:
+//! [length in bytes, represented as 4-byte integer] || [item in bytes]
+//!
+//!
+//! 3. The encoding of a list of items is defined as: (This is not implemented yet because
+//! there is no known struct that needs it yet, but can be added later easily)
+//! [No. of items in the list, represented as 4-byte integer] || encoding(item_0) || ....
+//!
+//! 4. The encoding of an ordered map where the keys are ordered by lexicographic order.
+//! Currently we only support key and value of type Vec. The encoding is defined as:
+//! [No. of key value pairs in the map, represented as 4-byte integer] || encode(key1) ||
+//! encode(value1) || encode(key2) || encode(value2)...
+//! where the pairs are appended following the lexicographic order of the key
+//!
+//! What is canonical serialization?
+//!
+//! Canonical serialization guarantees byte consistency when serializing an in-memory
+//! data structure. It is useful for situations where two parties want to efficiently compare
+//! data structures they independently maintain. It happens in consensus where
+//! independent validators need to agree on the state they independently compute. A cryptographic
+//! hash of the serialized data structure is what ultimately gets compared. In order for
+//! this to work, the serialization of the same data structures must be identical when computed
+//! by independent validators potentially running different implementations
+//! of the same spec in different languages.
+
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use failure::prelude::*;
+use std::{
+ collections::BTreeMap,
+ io::{Cursor, Read},
+ mem::size_of,
+};
+
+pub mod test_helper;
+
+#[cfg(test)]
+mod canonical_serialization_test;
+
+// use the signed 32-bit integer's max value as the maximum array length instead of
+// unsigned 32-bit integer. This gives us the opportunity to use the additional sign bit
+// to signal a length extension to support arrays longer than 2^31 in the future
+const ARRAY_MAX_LENGTH: usize = i32::max_value() as usize;
+
+pub trait CanonicalSerialize {
+ fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()>;
+}
+
+pub trait CanonicalSerializer {
+ fn encode_struct(&mut self, structure: &impl CanonicalSerialize) -> Result<&mut Self>
+ where
+ Self: std::marker::Sized,
+ {
+ structure.serialize(self)?;
+ Ok(self)
+ }
+
+ fn encode_u64(&mut self, v: u64) -> Result<&mut Self>;
+
+ fn encode_u32(&mut self, v: u32) -> Result<&mut Self>;
+
+ fn encode_u8(&mut self, v: u8) -> Result<&mut Self>;
+
+ fn encode_bool(&mut self, b: bool) -> Result<&mut Self>;
+
+ // Use this encoder when the length of the array is known to be fixed and always known at
+ // deserialization time. The raw bytes of the array without length prefix are encoded.
+ // For deserialization, use decode_bytes_with_len() which requires giving the length
+ // as input
+ fn encode_raw_bytes(&mut self, bytes: &[u8]) -> Result<&mut Self>;
+
+ // Use this encoder to encode variable length byte arrays whose length may not be known at
+ // deserialization time.
+ fn encode_variable_length_bytes(&mut self, v: &[u8]) -> Result<&mut Self>;
+
+ fn encode_btreemap(
+ &mut self,
+ v: &BTreeMap,
+ ) -> Result<&mut Self>;
+
+ fn encode_vec(&mut self, v: &[T]) -> Result<&mut Self>;
+}
+
+type Endianness = LittleEndian;
+
+/// An implementation of a simple canonical serialization format that implements the
+/// CanonicalSerializer trait using a byte vector.
+#[derive(Clone)]
+pub struct SimpleSerializer {
+ output: W,
+}
+
+impl Default for SimpleSerializer
+where
+ W: Default + std::io::Write,
+{
+ fn default() -> Self {
+ SimpleSerializer::new()
+ }
+}
+
+impl SimpleSerializer
+where
+ W: Default + std::io::Write,
+{
+ pub fn new() -> Self {
+ SimpleSerializer {
+ output: W::default(),
+ }
+ }
+
+ /// Create a SimpleSerializer on the fly and serialize `object`
+ pub fn serialize(object: &impl CanonicalSerialize) -> Result {
+ let mut serialzier = Self::default();
+ object.serialize(&mut serialzier)?;
+ Ok(serialzier.get_output())
+ }
+
+ /// Consume the SimpleSerializer and return the output
+ pub fn get_output(self) -> W {
+ self.output
+ }
+}
+
+impl CanonicalSerializer for SimpleSerializer
+where
+ W: std::io::Write,
+{
+ fn encode_u64(&mut self, v: u64) -> Result<&mut Self> {
+ self.output.write_u64::(v)?;
+ Ok(self)
+ }
+
+ fn encode_u32(&mut self, v: u32) -> Result<&mut Self> {
+ self.output.write_u32::(v)?;
+ Ok(self)
+ }
+
+ fn encode_u8(&mut self, v: u8) -> Result<&mut Self> {
+ self.output.write_u8(v)?;
+ Ok(self)
+ }
+
+ fn encode_bool(&mut self, b: bool) -> Result<&mut Self> {
+ let byte: u8 = if b { 1 } else { 0 };
+ self.output.write_u8(byte)?;
+ Ok(self)
+ }
+
+ fn encode_raw_bytes(&mut self, bytes: &[u8]) -> Result<&mut Self> {
+ self.output.write_all(bytes.as_ref())?;
+ Ok(self)
+ }
+
+ fn encode_variable_length_bytes(&mut self, v: &[u8]) -> Result<&mut Self> {
+ ensure!(
+ v.len() <= ARRAY_MAX_LENGTH,
+ "array length exceeded the maximum length limit. \
+ length: {}, Max length limit: {}",
+ v.len(),
+ ARRAY_MAX_LENGTH,
+ );
+
+ // first add the length as a 4-byte integer
+ self.output.write_u32::(v.len() as u32)?;
+ self.output.write_all(v)?;
+ Ok(self)
+ }
+
+ fn encode_btreemap(
+ &mut self,
+ v: &BTreeMap,
+ ) -> Result<&mut Self> {
+ ensure!(
+ v.len() <= ARRAY_MAX_LENGTH,
+ "map size exceeded the maximum limit. length: {}, max length limit: {}",
+ v.len(),
+ ARRAY_MAX_LENGTH,
+ );
+
+ // add the number of pairs in the map
+ self.output.write_u32::