├── rustfmt.toml ├── doc ├── design │ ├── wallet-arch.png │ ├── wallet-arch.puml │ ├── goals.md │ └── design.md ├── transaction │ ├── basic-transaction-wf.png │ └── basic-transaction-wf.puml ├── samples │ └── v3_api_node │ │ ├── package.json │ │ ├── readme.md │ │ ├── src │ │ └── index.js │ │ └── package-lock.json └── tls-setup.md ├── CODE_OF_CONDUCT.md ├── tests ├── data │ ├── v3_reqs │ │ ├── get_top_level.req.json │ │ ├── close_wallet.req.json │ │ ├── delete_wallet.req.json │ │ ├── open_wallet.req.json │ │ ├── change_password.req.json │ │ ├── init_secure_api.req.json │ │ ├── retrieve_info.req.json │ │ ├── create_wallet.req.json │ │ ├── create_config.req.json │ │ ├── create_wallet_invalid_mn.req.json │ │ ├── create_wallet_valid_mn.req.json │ │ └── init_send_tx.req.json │ └── v2_reqs │ │ ├── retrieve_info.req.json │ │ └── init_send_tx.req.json └── tor_dev_helper.rs ├── .cargo └── config ├── .gitignore ├── .ci ├── test.yml ├── general-jobs ├── release.yml └── windows-release.yml ├── impls ├── src │ ├── tor │ │ └── mod.rs │ ├── backends │ │ └── mod.rs │ ├── lifecycle │ │ └── mod.rs │ ├── node_clients │ │ ├── mod.rs │ │ └── resp_types.rs │ ├── client_utils │ │ └── mod.rs │ ├── adapters │ │ ├── file.rs │ │ ├── mod.rs │ │ └── slatepack.rs │ ├── lib.rs │ └── error.rs └── Cargo.toml ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── src ├── cli │ └── mod.rs ├── cmd │ ├── mod.rs │ └── wallet.rs ├── lib.rs ├── build │ └── build.rs └── bin │ └── grin-wallet.rs ├── config ├── Cargo.toml └── src │ ├── lib.rs │ └── comments.rs ├── integration ├── src │ └── lib.rs ├── Cargo.toml └── tests │ ├── dandelion.rs │ └── stratum.rs ├── libwallet ├── src │ ├── api_impl.rs │ ├── slatepack │ │ ├── mod.rs │ │ └── packer.rs │ ├── internal.rs │ ├── address.rs │ ├── lib.rs │ ├── slate_versions │ │ └── mod.rs │ ├── internal │ │ └── keys.rs │ └── api_impl │ │ ├── owner_updater.rs │ │ └── foreign.rs ├── Cargo.toml └── tests │ ├── slates │ └── v2.slate │ └── slate_versioning.rs ├── api ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── slate_versioning.rs ├── README.md ├── util ├── src │ ├── lib.rs │ └── ov3.rs └── Cargo.toml ├── controller ├── src │ ├── lib.rs │ └── error.rs ├── Cargo.toml └── tests │ ├── updater_thread.rs │ ├── self_send.rs │ ├── payment_proofs.rs │ ├── common │ └── mod.rs │ ├── ttl_cutoff.rs │ └── no_change.rs ├── Cargo.toml ├── .hooks └── pre-commit └── azure-pipelines.yml /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | edition = "2018" 3 | -------------------------------------------------------------------------------- /doc/design/wallet-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vcashorg/vcash-wallet/HEAD/doc/design/wallet-arch.png -------------------------------------------------------------------------------- /doc/transaction/basic-transaction-wf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vcashorg/vcash-wallet/HEAD/doc/transaction/basic-transaction-wf.png -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The Code of Conduct for this repository [can be found online](https://grin.mw/policies/code_of_conduct). -------------------------------------------------------------------------------- /tests/data/v3_reqs/get_top_level.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "get_top_level_directory", 4 | "params": { 5 | }, 6 | "id": 1 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/close_wallet.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "close_wallet", 4 | "params": { 5 | "name": null 6 | }, 7 | "id": 1 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/delete_wallet.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "delete_wallet", 4 | "params": { 5 | "name": null 6 | }, 7 | "id": 1 8 | } 9 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-Ctarget-feature=+crt-static"] 3 | [target.i686-pc-windows-msvc] 4 | rustflags = ["-Ctarget-feature=+crt-static"] 5 | -------------------------------------------------------------------------------- /tests/data/v2_reqs/retrieve_info.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "retrieve_summary_info", 4 | "params": [ 5 | true, 6 | 1 7 | ], 8 | "id": 1 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | .grin* 4 | node* 5 | !node_clients 6 | !node_clients.rs 7 | target 8 | */Cargo.lock 9 | *.iml 10 | grin.log 11 | wallet.seed 12 | test_output 13 | .idea/ 14 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/open_wallet.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "open_wallet", 4 | "params": { 5 | "name": null, 6 | "password": "passwoid" 7 | }, 8 | "id": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/change_password.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "change_password", 4 | "params": { 5 | "name": null, 6 | "old": "passwoid", 7 | "new": "password" 8 | }, 9 | "id": 1 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/init_secure_api.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "init_secure_api", 4 | "params": { 5 | "ecdh_pubkey": "03b3c18c9a38783d105e238953b1638b021ba7456d87a5c085b3bdb75777b4c490" 6 | }, 7 | "id": 1 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/retrieve_info.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "retrieve_summary_info", 4 | "params": { 5 | "token": null, 6 | "refresh_from_node": true, 7 | "minimum_confirmations": 1 8 | }, 9 | "id": 1 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/create_wallet.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "create_wallet", 4 | "params": { 5 | "name": null, 6 | "mnemonic": null, 7 | "mnemonic_length": 32, 8 | "password": "passwoid" 9 | }, 10 | "id": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/create_config.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "create_config", 4 | "params": { 5 | "chain_type": "AutomatedTesting", 6 | "wallet_config": null, 7 | "logging_config": null, 8 | "tor_config": null 9 | }, 10 | "id": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/create_wallet_invalid_mn.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "create_wallet", 4 | "params": { 5 | "name": null, 6 | "mnemonic": "this is not valid", 7 | "mnemonic_length": 32, 8 | "password": "passwoid" 9 | }, 10 | "id": 1 11 | } 12 | -------------------------------------------------------------------------------- /doc/samples/v3_api_node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-sample", 3 | "version": "0.0.1", 4 | "description": "Sample of connecting to the secure OwnerAPI via node", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "jayson": "^3.2.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/create_wallet_valid_mn.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "create_wallet", 4 | "params": { 5 | "name": null, 6 | "mnemonic": "fat twenty mean degree forget shell check candy immense awful flame next during february bulb bike sun wink theory day kiwi embrace peace lunch", 7 | "mnemonic_length": 32, 8 | "password": "passwoid" 9 | }, 10 | "id": 1 11 | } 12 | -------------------------------------------------------------------------------- /.ci/test.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: 'cargo test --all' 3 | displayName: Windows Cargo Test 4 | condition: and(eq( variables['Agent.OS'], 'Windows_NT' ), eq( variables['CI_JOB'], 'test-all' )) 5 | - script: 'cargo test --all' 6 | displayName: macOS Cargo Test 7 | condition: and(eq( variables['Agent.OS'], 'Darwin' ), eq( variables['CI_JOB'], 'test-all' )) 8 | - script: '.ci/general-jobs' 9 | displayName: Linux Cargo Test 10 | condition: eq( variables['Agent.OS'], 'Linux' ) -------------------------------------------------------------------------------- /tests/data/v2_reqs/init_send_tx.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "init_send_tx", 4 | "params": { 5 | "args": { 6 | "src_acct_name": null, 7 | "amount": "600000000000", 8 | "minimum_confirmations": 2, 9 | "max_outputs": 500, 10 | "num_change_outputs": 1, 11 | "selection_strategy_is_use_all": true, 12 | "message": "my message", 13 | "target_slate_version": null, 14 | "payment_proof_recipient_address": null, 15 | "ttl_blocks": null, 16 | "send_args": null 17 | } 18 | }, 19 | "id": 1 20 | } 21 | 22 | -------------------------------------------------------------------------------- /tests/data/v3_reqs/init_send_tx.req.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "init_send_tx", 4 | "params": { 5 | "token": null, 6 | "args": { 7 | "src_acct_name": null, 8 | "amount": "600000000000", 9 | "minimum_confirmations": 2, 10 | "max_outputs": 500, 11 | "num_change_outputs": 1, 12 | "selection_strategy_is_use_all": true, 13 | "message": "my message", 14 | "target_slate_version": null, 15 | "payment_proof_recipient_address": null, 16 | "ttl_blocks": null, 17 | "send_args": null 18 | } 19 | }, 20 | "id": 1 21 | } 22 | 23 | -------------------------------------------------------------------------------- /impls/src/tor/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | pub mod config; 16 | pub mod process; 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod cli; 16 | 17 | pub use cli::command_loop; 18 | -------------------------------------------------------------------------------- /config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_config" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Configuration for grin wallet , a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin-wallet" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | workspace = ".." 10 | edition = "2018" 11 | 12 | [dependencies] 13 | rand = "0.6" 14 | serde = "1" 15 | serde_derive = "1" 16 | toml = "0.5" 17 | dirs = "2.0" 18 | 19 | grin_wallet_util = { path = "../util", version = "4.1.0-alpha.1" } 20 | 21 | [dev-dependencies] 22 | pretty_assertions = "0.6" 23 | -------------------------------------------------------------------------------- /impls/src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod lmdb; 16 | 17 | pub use self::lmdb::{wallet_db_exists, LMDBBackend}; 18 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod wallet; 16 | pub mod wallet_args; 17 | 18 | pub use self::wallet::wallet_command; 19 | -------------------------------------------------------------------------------- /impls/src/lifecycle/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod default; 16 | mod seed; 17 | 18 | pub use self::default::DefaultLCProvider; 19 | -------------------------------------------------------------------------------- /impls/src/node_clients/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod http; 16 | mod resp_types; 17 | 18 | pub use self::http::HTTPNodeClient; 19 | -------------------------------------------------------------------------------- /impls/src/client_utils/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod client; 16 | pub mod json_rpc; 17 | 18 | pub use client::{Client, Error as ClientError}; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /integration/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Grin integration test crate 16 | 17 | #![deny(non_upper_case_globals)] 18 | #![deny(non_camel_case_types)] 19 | #![deny(non_snake_case)] 20 | #![deny(unused_mut)] 21 | #![warn(missing_docs)] 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | #[macro_use] 15 | extern crate lazy_static; 16 | #[macro_use] 17 | extern crate clap; 18 | 19 | use grin_wallet_config as config; 20 | use grin_wallet_util::grin_api as api; 21 | use grin_wallet_util::grin_util as util; 22 | 23 | mod cli; 24 | pub mod cmd; 25 | -------------------------------------------------------------------------------- /libwallet/src/api_impl.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! lower-level wallet functions which build upon core::libtx to perform wallet 16 | //! operations 17 | 18 | #![deny(non_upper_case_globals)] 19 | #![deny(non_camel_case_types)] 20 | #![deny(non_snake_case)] 21 | #![deny(unused_mut)] 22 | #![warn(missing_docs)] 23 | 24 | pub mod foreign; 25 | pub mod owner; 26 | pub mod owner_updater; 27 | pub mod types; 28 | -------------------------------------------------------------------------------- /libwallet/src/slatepack/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Functions and types for handling Slatepack transactions 16 | 17 | mod address; 18 | mod armor; 19 | mod packer; 20 | mod types; 21 | 22 | pub use self::address::SlatepackAddress; 23 | pub use self::armor::{max_size, min_size, SlatepackArmor}; 24 | pub use self::packer::{Slatepacker, SlatepackerArgs}; 25 | pub use self::types::{Slatepack, SlatepackBin}; 26 | -------------------------------------------------------------------------------- /.ci/general-jobs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Grin Developers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script contains test-related jobs. 18 | 19 | case "${CI_JOB}" in 20 | "test") 21 | for dir in ${CI_JOB_ARGS}; do 22 | printf "executing tests in directory \`%s\`...\n" "${dir}" 23 | cd "${dir}" && \ 24 | cargo test --release && \ 25 | cd - > /dev/null || exit 1 26 | done 27 | ;; 28 | esac -------------------------------------------------------------------------------- /libwallet/src/internal.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! lower-level wallet functions which build upon core::libtx to perform wallet 16 | //! operations 17 | 18 | #![deny(non_upper_case_globals)] 19 | #![deny(non_camel_case_types)] 20 | #![deny(non_snake_case)] 21 | #![deny(unused_mut)] 22 | #![warn(missing_docs)] 23 | 24 | pub mod keys; 25 | pub mod scan; 26 | pub mod selection; 27 | pub mod token_scan; 28 | pub mod tx; 29 | pub mod updater; 30 | -------------------------------------------------------------------------------- /impls/src/node_clients/resp_types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Derived from https://github.com/apoelstra/rust-jsonrpc 15 | 16 | //! JSON RPC Types for V2 node client 17 | 18 | #[derive(Debug, Deserialize)] 19 | pub struct GetTipResp { 20 | pub height: u64, 21 | pub last_block_pushed: String, 22 | pub prev_block_to_last: String, 23 | pub total_difficulty: u64, 24 | } 25 | 26 | #[derive(Debug, Deserialize)] 27 | pub struct GetVersionResp { 28 | pub node_version: String, 29 | pub block_header_version: u16, 30 | } 31 | -------------------------------------------------------------------------------- /doc/samples/v3_api_node/readme.md: -------------------------------------------------------------------------------- 1 | # Connecting to the wallet's V3 Owner API from Node 2 | 3 | This is a small sample with code that demonstrates how to initialize the Wallet V3's Secure API and call API functions through it. 4 | 5 | To run this sample: 6 | 7 | First run the Owner API: 8 | 9 | ```.sh 10 | grin-wallet owner_api 11 | ``` 12 | 13 | This sample doesn't use the authentication specified in the wallet's `.api_secret`, so before running the owner_api please ensure api authentication is commented out in `grin-wallet.toml`. Including the authentication token as part of the request is a function of your json-rpc client library of choice, so it's not included in the sample to make setup a bit simpler. 14 | 15 | ensure the client url in `src\index.js` is set correctly: 16 | 17 | ```.sh 18 | const client = jayson.client.http('http://localhost:3420/v3/owner'); 19 | ``` 20 | 21 | Then (assuming node.js and npm are installed on the system): 22 | 23 | ```.sh 24 | npm install 25 | node src/index.json 26 | ``` 27 | 28 | Feel free to play around with the sample, modifying it to call whatever functions you'd like to see in operation! 29 | -------------------------------------------------------------------------------- /api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_api" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Grin Wallet API" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin-wallet" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | exclude = ["**/*.grin", "**/*.grin2"] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | failure = "0.1" 14 | failure_derive = "0.1" 15 | log = "0.4" 16 | uuid = { version = "0.8", features = ["serde", "v4"] } 17 | serde = "1" 18 | rand = "0.6" 19 | serde_derive = "1" 20 | serde_json = "1" 21 | easy-jsonrpc-mw = "0.5.4" 22 | chrono = { version = "0.4.11", features = ["serde"] } 23 | ring = "0.16" 24 | base64 = "0.12" 25 | ed25519-dalek = "1.0.0-pre.4" 26 | 27 | grin_wallet_libwallet = { path = "../libwallet", version = "4.1.0-alpha.1" } 28 | grin_wallet_config = { path = "../config", version = "4.1.0-alpha.1" } 29 | grin_wallet_impls = { path = "../impls", version = "4.1.0-alpha.1" } 30 | grin_wallet_util = { path = "../util", version = "4.1.0-alpha.1" } 31 | 32 | [dev-dependencies] 33 | serde_json = "1" 34 | tempfile = "3.1" 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Wiki](https://img.shields.io/badge/doc-wiki-blue.svg)](https://github.com/vcashorg/vcash/wiki/) 2 | [![License](https://img.shields.io/github/license/mimblewimble/grin-wallet.svg)](https://github.com/vcashorg/vcash-wallet/blob/master/LICENSE) 3 | 4 | # VCash Wallet 5 | 6 | This is the reference implementation of [VCash's](https://github.com/vcashorg/vcash) wallet. It consists of 2 major pieces: 7 | 8 | * The VCash Wallet APIs, which are intended for use by VCash community wallet developers. The wallet APIs can be directly linked into other projects or invoked via a JSON-RPC interface. 9 | 10 | * A reference command-line wallet, which provides a baseline wallet for VCash and demonstrates how the wallet APIs should be called. 11 | 12 | # Usage 13 | 14 | To use the command-line wallet, we recommend using the latest release from the [Releases page](https://github.com/vcashorg/vcash-wallet/releases). There are distributions for Linux and MacOS. 15 | 16 | Full documentation outlining how to use the command line wallet can be found on [VCash's Wiki](https://github.com/vcashorg/vcash/wiki/How-to-use-the-Vcash-wallet) 17 | 18 | # License 19 | 20 | Apache License v2.0 21 | 22 | -------------------------------------------------------------------------------- /util/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Utilities and re-exports 16 | 17 | #![deny(non_upper_case_globals)] 18 | #![deny(non_camel_case_types)] 19 | #![deny(non_snake_case)] 20 | #![deny(unused_mut)] 21 | #![warn(missing_docs)] 22 | 23 | #[macro_use] 24 | extern crate serde_derive; 25 | 26 | mod ov3; 27 | pub use ov3::OnionV3Address; 28 | pub use ov3::OnionV3Error as OnionV3AddressError; 29 | 30 | #[allow(missing_docs)] 31 | pub mod byte_ser; 32 | 33 | pub use grin_api; 34 | pub use grin_chain; 35 | pub use grin_core; 36 | pub use grin_keychain; 37 | pub use grin_store; 38 | pub use grin_util; 39 | -------------------------------------------------------------------------------- /libwallet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_libwallet" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin-wallet" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | exclude = ["**/*.grin", "**/*.grin2"] 10 | #build = "src/build/build.rs" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | blake2-rfc = "0.2" 15 | failure = "0.1" 16 | failure_derive = "0.1" 17 | rand = "0.6" 18 | serde = "1" 19 | serde_derive = "1" 20 | serde_json = "1" 21 | log = "0.4" 22 | uuid = { version = "0.8", features = ["serde", "v4"] } 23 | chrono = { version = "0.4.11", features = ["serde"] } 24 | lazy_static = "1" 25 | strum = "0.18" 26 | strum_macros = "0.18" 27 | ed25519-dalek = "1.0.0-pre.4" 28 | x25519-dalek = "0.6" 29 | base64 = "0.9" 30 | regex = "1.3" 31 | sha2 = "0.8" 32 | bs58 = "0.3" 33 | age = "0.4" 34 | curve25519-dalek = "2.1" 35 | secrecy = "0.6" 36 | bech32 = "0.7" 37 | byteorder = "1.3" 38 | 39 | grin_wallet_util = { path = "../util", version = "4.1.0-alpha.1" } 40 | grin_wallet_config = { path = "../config", version = "4.1.0-alpha.1" } 41 | 42 | -------------------------------------------------------------------------------- /config/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Crate wrapping up the Grin binary and configuration file 16 | 17 | #![deny(non_upper_case_globals)] 18 | #![deny(non_camel_case_types)] 19 | #![deny(non_snake_case)] 20 | #![deny(unused_mut)] 21 | #![warn(missing_docs)] 22 | 23 | #[macro_use] 24 | extern crate serde_derive; 25 | 26 | use grin_wallet_util::grin_core as core; 27 | use grin_wallet_util::grin_util as util; 28 | 29 | mod comments; 30 | pub mod config; 31 | pub mod types; 32 | 33 | pub use crate::config::{ 34 | config_file_exists, initial_setup_wallet, GRIN_WALLET_DIR, WALLET_CONFIG_FILE_NAME, 35 | }; 36 | pub use crate::types::{ 37 | ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorConfig, WalletConfig, 38 | }; 39 | -------------------------------------------------------------------------------- /controller/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Library module for the main wallet functionalities provided by Grin. 16 | 17 | #[macro_use] 18 | extern crate prettytable; 19 | 20 | #[macro_use] 21 | extern crate log; 22 | #[macro_use] 23 | extern crate lazy_static; 24 | use failure; 25 | use grin_wallet_api as apiwallet; 26 | use grin_wallet_config as config; 27 | use grin_wallet_impls as impls; 28 | use grin_wallet_libwallet as libwallet; 29 | use grin_wallet_util::grin_api as api; 30 | use grin_wallet_util::grin_core as core; 31 | use grin_wallet_util::grin_keychain as keychain; 32 | use grin_wallet_util::grin_util as util; 33 | 34 | pub mod command; 35 | pub mod controller; 36 | pub mod display; 37 | mod error; 38 | 39 | pub use crate::error::{Error, ErrorKind}; 40 | -------------------------------------------------------------------------------- /controller/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_controller" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Controllers for grin wallet instantiation" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin-wallet" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | exclude = ["**/*.grin", "**/*.grin2"] 10 | #build = "src/build/build.rs" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | failure = "0.1" 15 | failure_derive = "0.1" 16 | futures = "0.3" 17 | hyper = "0.13" 18 | rand = "0.7" 19 | serde = "1" 20 | serde_derive = "1" 21 | serde_json = "1" 22 | log = "0.4" 23 | prettytable-rs = "0.8" 24 | ring = "0.16" 25 | term = "0.6" 26 | tokio = { version = "0.2", features = ["full"] } 27 | uuid = { version = "0.8", features = ["serde", "v4"] } 28 | url = "2.1" 29 | chrono = { version = "0.4.11", features = ["serde"] } 30 | easy-jsonrpc-mw = "0.5.4" 31 | lazy_static = "1" 32 | 33 | grin_wallet_util = { path = "../util", version = "4.1.0-alpha.1" } 34 | 35 | grin_wallet_api = { path = "../api", version = "4.1.0-alpha.1" } 36 | grin_wallet_impls = { path = "../impls", version = "4.1.0-alpha.1" } 37 | grin_wallet_libwallet = { path = "../libwallet", version = "4.1.0-alpha.1" } 38 | grin_wallet_config = { path = "../config", version = "4.1.0-alpha.1" } 39 | 40 | [dev-dependencies] 41 | ed25519-dalek = "1.0.0-pre.4" 42 | -------------------------------------------------------------------------------- /impls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_impls" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Concrete types derived from libwallet traits" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin-wallet" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | exclude = ["**/*.grin", "**/*.grin2"] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | blake2-rfc = "0.2" 14 | failure = "0.1" 15 | failure_derive = "0.1" 16 | futures = "0.3" 17 | rand = "0.6" 18 | serde = "1" 19 | serde_derive = "1" 20 | serde_json = "1" 21 | log = "0.4" 22 | ring = "0.16" 23 | tokio = { version = "0.2", features = ["full"] } 24 | uuid = { version = "0.8", features = ["serde", "v4"] } 25 | chrono = { version = "0.4.11", features = ["serde"] } 26 | crossbeam-utils = "0.7" 27 | 28 | #http client (copied from grin) 29 | http = "0.2" 30 | hyper-rustls = "0.20" 31 | hyper-timeout = "0.3" 32 | 33 | #Socks/Tor 34 | byteorder = "1" 35 | hyper = "0.13" 36 | hyper-socks2-mw = "0.4" 37 | ed25519-dalek = "1.0.0-pre.4" 38 | x25519-dalek = "0.6" 39 | data-encoding = "2" 40 | regex = "1.3" 41 | timer = "0.2" 42 | sysinfo = "0.14" 43 | 44 | grin_wallet_util = { path = "../util", version = "4.1.0-alpha.1" } 45 | grin_wallet_config = { path = "../config", version = "4.1.0-alpha.1" } 46 | grin_wallet_libwallet = { path = "../libwallet", version = "4.1.0-alpha.1" } 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vcash_wallet" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/vcashorg/vcash-wallet" 8 | keywords = ["vcash", "crypto", "grin", "mimblewimble" ] 9 | readme = "README.md" 10 | exclude = ["**/*.grin", "**/*.grin2"] 11 | build = "src/build/build.rs" 12 | edition = "2018" 13 | 14 | [[bin]] 15 | name = "vcash-wallet" 16 | path = "src/bin/grin-wallet.rs" 17 | 18 | [workspace] 19 | members = ["api", "config", "controller", "impls", "libwallet", "util"] 20 | exclude = ["integration"] 21 | 22 | [dependencies] 23 | clap = { version = "2.33", features = ["yaml"] } 24 | rpassword = "4.0" 25 | failure = "0.1" 26 | failure_derive = "0.1" 27 | prettytable-rs = "0.8" 28 | log = "0.4" 29 | linefeed = "0.6" 30 | semver = "0.10" 31 | rustyline = "6" 32 | lazy_static = "1" 33 | 34 | grin_wallet_api = { path = "./api", version = "4.1.0-alpha.1" } 35 | grin_wallet_impls = { path = "./impls", version = "4.1.0-alpha.1" } 36 | grin_wallet_libwallet = { path = "./libwallet", version = "4.1.0-alpha.1" } 37 | grin_wallet_controller = { path = "./controller", version = "4.1.0-alpha.1" } 38 | grin_wallet_config = { path = "./config", version = "4.1.0-alpha.1" } 39 | 40 | grin_wallet_util = { path = "./util", version = "4.1.0-alpha.1" } 41 | 42 | [build-dependencies] 43 | built = { version = "0.4", features = ["git2"]} 44 | 45 | [dev-dependencies] 46 | url = "2.1" 47 | serde = "1" 48 | serde_derive = "1" 49 | serde_json = "1" 50 | easy-jsonrpc-mw = "0.5.4" 51 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 The Grin Developers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | rustfmt --version &>/dev/null 18 | if [ $? != 0 ]; then 19 | printf "[pre_commit] \033[0;31merror\033[0m: \"rustfmt\" not available. \n" 20 | printf "[pre_commit] \033[0;31merror\033[0m: rustfmt can be installed via - \n" 21 | printf "[pre_commit] $ rustup component add rustfmt-preview \n" 22 | exit 1 23 | fi 24 | 25 | problem_files=() 26 | 27 | # first collect all the files that need reformatting 28 | for file in $(git diff --name-only --cached); do 29 | if [ ${file: -3} == ".rs" ]; then 30 | rustfmt --check $file &>/dev/null 31 | if [ $? != 0 ]; then 32 | problem_files+=($file) 33 | fi 34 | fi 35 | done 36 | 37 | if [ ${#problem_files[@]} == 0 ]; then 38 | # nothing to do 39 | printf "[pre_commit] rustfmt \033[0;32mok\033[0m \n" 40 | else 41 | # reformat the files that need it and re-stage them. 42 | printf "[pre_commit] the following files were rustfmt'd before commit: \n" 43 | for file in ${problem_files[@]}; do 44 | rustfmt $file 45 | git add $file 46 | printf "\033[0;32m $file\033[0m \n" 47 | done 48 | fi 49 | 50 | exit 0 51 | -------------------------------------------------------------------------------- /integration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_integration" 3 | version = "1.1.0" 4 | authors = ["Grin Developers "] 5 | description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/mimblewimble/grin" 8 | keywords = [ "crypto", "grin", "mimblewimble" ] 9 | workspace = ".." 10 | edition = "2018" 11 | 12 | [dependencies] 13 | hyper = "0.12" 14 | futures = "0.1" 15 | http = "0.1" 16 | itertools = "0.7" 17 | rand = "0.5" 18 | serde = "1" 19 | log = "0.4" 20 | serde_derive = "1" 21 | serde_json = "1" 22 | chrono = "0.4.4" 23 | tokio = "0.1.11" 24 | blake2-rfc = "0.2" 25 | bufstream = "0.1" 26 | 27 | #grin_apiwallet = { path = "../apiwallet", version = "1.1.0" } 28 | #grin_libwallet = { path = "../libwallet", version = "1.1.0" } 29 | #grin_refwallet = { path = "../refwallet", version = "1.1.0" } 30 | #grin_wallet_config = { path = "../config", version = "1.1.0" } 31 | 32 | #grin_core = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 33 | #grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 34 | #grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 35 | #grin_util = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 36 | #grin_api = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 37 | #grin_store = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 38 | #grin_p2p = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 39 | #grin_servers = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } 40 | -------------------------------------------------------------------------------- /src/build/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Build hooks to spit out version+build time info 16 | 17 | use built; 18 | use std::env; 19 | use std::path::{Path, PathBuf}; 20 | use std::process::Command; 21 | 22 | fn main() { 23 | // Setting up git hooks in the project: rustfmt and so on. 24 | let git_hooks = format!( 25 | "git config core.hooksPath {}", 26 | PathBuf::from("./.hooks").to_str().unwrap() 27 | ); 28 | 29 | if cfg!(target_os = "windows") { 30 | Command::new("cmd") 31 | .args(&["/C", &git_hooks]) 32 | .output() 33 | .expect("failed to execute git config for hooks"); 34 | } else { 35 | Command::new("sh") 36 | .args(&["-c", &git_hooks]) 37 | .output() 38 | .expect("failed to execute git config for hooks"); 39 | } 40 | 41 | // build and versioning information 42 | let mut opts = built::Options::default(); 43 | opts.set_dependencies(true); 44 | let out_dir_path = format!("{}{}", env::var("OUT_DIR").unwrap(), "/built.rs"); 45 | // don't fail the build if something's missing, may just be cargo release 46 | let _ = built::write_built_file_with_opts( 47 | &opts, 48 | Path::new(env!("CARGO_MANIFEST_DIR")), 49 | Path::new(&out_dir_path), 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /libwallet/src/address.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Develope; 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Functions defining wallet 'addresses', i.e. ed2559 keys based on 16 | //! a derivation path 17 | 18 | use crate::grin_util::secp::key::SecretKey; 19 | use crate::Error; 20 | use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType}; 21 | 22 | use crate::blake2::blake2b::blake2b; 23 | 24 | /// Derive a secret key given a derivation path and index 25 | pub fn address_from_derivation_path( 26 | keychain: &K, 27 | parent_key_id: &Identifier, 28 | index: u32, 29 | ) -> Result 30 | where 31 | K: Keychain, 32 | { 33 | let mut key_path = parent_key_id.to_path(); 34 | // An output derivation for acct m/0 35 | // is m/0/0/0, m/0/0/1 (for instance), m/1 is m/1/0/0, m/1/0/1 36 | // Address generation path should be 37 | // for m/0: m/0/1/0, m/0/1/1 38 | // for m/1: m/1/1/0, m/1/1/1 39 | key_path.path[1] = ChildNumber::from(1); 40 | key_path.depth += 1; 41 | key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index); 42 | let key_id = Identifier::from_path(&key_path); 43 | let sec_key = keychain.derive_key(0, &key_id, SwitchCommitmentType::None)?; 44 | let hashed = blake2b(32, &[], &sec_key.0[..]); 45 | Ok(SecretKey::from_slice( 46 | &keychain.secp(), 47 | &hashed.as_bytes()[..], 48 | )?) 49 | } 50 | -------------------------------------------------------------------------------- /api/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Higher level wallet functions which can be used by callers to operate 16 | //! on the wallet, as well as helpers to invoke and instantiate wallets 17 | //! and listeners 18 | 19 | #![deny(non_upper_case_globals)] 20 | #![deny(non_camel_case_types)] 21 | #![deny(non_snake_case)] 22 | #![deny(unused_mut)] 23 | #![warn(missing_docs)] 24 | 25 | use grin_wallet_config as config; 26 | use grin_wallet_util::grin_core as core; 27 | use grin_wallet_util::grin_keychain as keychain; 28 | use grin_wallet_util::grin_util as util; 29 | extern crate grin_wallet_impls as impls; 30 | extern crate grin_wallet_libwallet as libwallet; 31 | 32 | extern crate failure_derive; 33 | #[macro_use] 34 | extern crate serde_derive; 35 | extern crate serde_json; 36 | 37 | #[macro_use] 38 | extern crate log; 39 | 40 | mod foreign; 41 | mod foreign_rpc; 42 | 43 | mod owner; 44 | mod owner_rpc; 45 | 46 | mod types; 47 | 48 | pub use crate::foreign::{Foreign, ForeignCheckMiddleware, ForeignCheckMiddlewareFn}; 49 | pub use crate::foreign_rpc::ForeignRpc; 50 | pub use crate::owner::{try_slatepack_sync_workflow, Owner}; 51 | pub use crate::owner_rpc::OwnerRpc; 52 | 53 | pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client; 54 | pub use crate::foreign_rpc::run_doctest_foreign; 55 | pub use crate::owner_rpc::run_doctest_owner; 56 | 57 | pub use types::{ 58 | ECDHPubkey, Ed25519SecretKey, EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, 59 | JsonId, Token, 60 | }; 61 | -------------------------------------------------------------------------------- /util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grin_wallet_util" 3 | version = "4.1.0-alpha.1" 4 | authors = ["Grin Developers "] 5 | description = "Util, for generic utilities and to re-export grin crates" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/vcashorg/vcash-wallet" 8 | keywords = [ "crypto", "vcash", "mimblewimble" ] 9 | workspace = ".." 10 | edition = "2018" 11 | 12 | [dependencies] 13 | rand = "0.6" 14 | serde = "1" 15 | serde_derive = "1" 16 | ed25519-dalek = "1.0.0-pre.4" 17 | data-encoding = "2" 18 | sha3 = "0.8" 19 | 20 | # For Release 21 | #grin_core = "4.0.0" 22 | #grin_keychain = "4.0.0" 23 | #grin_chain = "4.0.0" 24 | #grin_util = "4.0.0" 25 | #grin_api = "4.0.0" 26 | #grin_store = "4.0.0" 27 | 28 | # For beta release 29 | 30 | # grin_core = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0"} 31 | # grin_keychain = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0" } 32 | # grin_chain = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0" } 33 | # grin_util = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0" } 34 | # grin_api = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0" } 35 | # grin_store = { git = "https://github.com/vcashorg/vcash", tag = "v4.0.0" } 36 | 37 | # For bleeding edge 38 | grin_core = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 39 | grin_keychain = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 40 | grin_chain = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 41 | grin_util = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 42 | grin_api = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 43 | grin_store = { git = "https://github.com/vcashorg/vcash", branch = "vcash" } 44 | 45 | # For local testing 46 | #grin_core = { path = "../../vcash/core"} 47 | #grin_keychain = { path = "../../vcash/keychain"} 48 | #grin_chain = { path = "../../vcash/chain"} 49 | #grin_util = { path = "../../vcash/util"} 50 | #grin_api = { path = "../../vcash/api"} 51 | #grin_store = { path = "../../vcash/store"} 52 | 53 | [dev-dependencies] 54 | pretty_assertions = "0.6" 55 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Grin Developers 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | trigger: 16 | branches: 17 | include: 18 | - master 19 | tags: 20 | include: ['*'] 21 | 22 | pr: 23 | branches: 24 | include: ['*'] 25 | 26 | variables: 27 | RUST_BACKTRACE: '1' 28 | RUSTFLAGS: '-C debug-assertions' 29 | 30 | jobs: 31 | - job: linux 32 | pool: 33 | vmImage: ubuntu-latest 34 | strategy: 35 | matrix: 36 | config/libwallet/api: 37 | CI_JOB: test 38 | CI_JOB_ARGS: config libwallet api 39 | impls: 40 | CI_JOB: test 41 | CI_JOB_ARGS: impls 42 | controller/all: 43 | CI_JOB: test 44 | CI_JOB_ARGS: controller . 45 | release: 46 | CI_JOB: release 47 | PLATFORM: linux-amd64 48 | steps: 49 | - template: '.ci/test.yml' 50 | - template: '.ci/release.yml' 51 | - job: macos 52 | pool: 53 | vmImage: macos-latest 54 | strategy: 55 | matrix: 56 | test: 57 | CI_JOB: test-all 58 | release: 59 | CI_JOB: release 60 | PLATFORM: macos 61 | steps: 62 | - script: | 63 | brew uninstall llvm 64 | displayName: macOS Uninstall LLVM 65 | - template: '.ci/test.yml' 66 | - template: '.ci/release.yml' 67 | - job: windows 68 | pool: 69 | vmImage: windows-latest 70 | strategy: 71 | matrix: 72 | test: 73 | CI_JOB: test-all 74 | release: 75 | CI_JOB: release 76 | PLATFORM: win-x64 77 | steps: 78 | - script: | 79 | choco install -y llvm 80 | displayName: Windows Install LLVM 81 | - template: '.ci/test.yml' 82 | - template: '.ci/windows-release.yml' 83 | -------------------------------------------------------------------------------- /src/cmd/wallet.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::cmd::wallet_args; 16 | use crate::config::GlobalWalletConfig; 17 | use clap::ArgMatches; 18 | use grin_wallet_libwallet::NodeClient; 19 | use semver::Version; 20 | use std::thread; 21 | use std::time::Duration; 22 | 23 | const MIN_COMPAT_NODE_VERSION: &str = "4.0.0-alpha.1"; 24 | 25 | pub fn wallet_command( 26 | wallet_args: &ArgMatches<'_>, 27 | config: GlobalWalletConfig, 28 | mut node_client: C, 29 | ) -> i32 30 | where 31 | C: NodeClient + 'static, 32 | { 33 | // just get defaults from the global config 34 | let wallet_config = config.members.clone().unwrap().wallet; 35 | 36 | let tor_config = config.members.unwrap().tor; 37 | 38 | // Check the node version info, and exit with report if we're not compatible 39 | let global_wallet_args = wallet_args::parse_global_args(&wallet_config, &wallet_args) 40 | .expect("Can't read configuration file"); 41 | node_client.set_node_api_secret(global_wallet_args.node_api_secret.clone()); 42 | 43 | // This will also cache the node version info for calls to foreign API check middleware 44 | if let Some(v) = node_client.clone().get_version_info() { 45 | if Version::parse(&v.node_version) < Version::parse(MIN_COMPAT_NODE_VERSION) { 46 | println!("The VCash Node in use (version {}) is outdated and incompatible with this wallet version.", v.node_version); 47 | println!( 48 | "Please update the node to version {} or later and try again.", 49 | MIN_COMPAT_NODE_VERSION 50 | ); 51 | return 1; 52 | } 53 | } 54 | // ... if node isn't available, allow offline functions 55 | 56 | let res = wallet_args::wallet_command( 57 | wallet_args, 58 | wallet_config, 59 | tor_config, 60 | node_client, 61 | false, 62 | |_| {}, 63 | ); 64 | 65 | // we need to give log output a chance to catch up before exiting 66 | thread::sleep(Duration::from_millis(100)); 67 | 68 | if let Err(e) = res { 69 | println!("Wallet command failed: {}", e); 70 | 1 71 | } else { 72 | println!( 73 | "Command '{}' completed successfully", 74 | wallet_args.subcommand().0 75 | ); 76 | 0 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.ci/release.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: 'cargo test --all' 3 | displayName: Cargo Test All 4 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 5 | - script: 'cargo build --release' 6 | displayName: Build Release 7 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 8 | - script: | 9 | MY_TAG="$(Build.SourceBranch)" 10 | MY_TAG=${MY_TAG#refs/tags/} 11 | echo $MY_TAG 12 | echo "##vso[task.setvariable variable=build.my_tag]$MY_TAG" 13 | echo "##vso[task.setvariable variable=build.platform]$PLATFORM" 14 | displayName: "Create my tag variable" 15 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 16 | - task: CopyFiles@2 17 | displayName: Copy assets 18 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 19 | inputs: 20 | sourceFolder: '$(Build.SourcesDirectory)/target/release' 21 | contents: 'grin-wallet' 22 | targetFolder: '$(Build.BinariesDirectory)/grin-wallet' 23 | - task: ArchiveFiles@2 24 | displayName: Gather assets 25 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 26 | inputs: 27 | rootFolderOrFile: '$(Build.BinariesDirectory)/grin-wallet' 28 | archiveType: 'tar' 29 | tarCompression: 'gz' 30 | archiveFile: '$(Build.ArtifactStagingDirectory)/grin-wallet-$(build.my_tag)-$(build.platform).tar.gz' 31 | - script: | 32 | cd $(Build.ArtifactStagingDirectory) && openssl sha256 grin-wallet-$(build.my_tag)-$(build.platform).tar.gz > grin-wallet-$(build.my_tag)-$(build.platform)-sha256sum.txt 33 | displayName: Create Checksum 34 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 35 | - task: GithubRelease@0 36 | displayName: Github release 37 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 38 | inputs: 39 | gitHubConnection: 'ignopeverell' 40 | repositoryName: 'mimblewimble/grin-wallet' 41 | action: 'edit' 42 | target: '$(build.sourceVersion)' 43 | tagSource: 'manual' 44 | tag: '$(build.my_tag)' 45 | assets: | 46 | $(Build.ArtifactStagingDirectory)/grin-wallet-$(build.my_tag)-$(build.platform).tar.gz 47 | $(Build.ArtifactStagingDirectory)/grin-wallet-$(build.my_tag)-$(build.platform)-sha256sum.txt 48 | title: '$(build.my_tag)' 49 | assetUploadMode: 'replace' 50 | addChangeLog: true -------------------------------------------------------------------------------- /impls/src/adapters/file.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// File Output 'plugin' implementation 16 | use std::fs::File; 17 | use std::io::{Read, Write}; 18 | 19 | use crate::libwallet::{Error, ErrorKind, Slate, SlateVersion, VersionedBinSlate, VersionedSlate}; 20 | use crate::{SlateGetter, SlatePutter}; 21 | use grin_wallet_util::byte_ser; 22 | use std::convert::TryFrom; 23 | use std::path::PathBuf; 24 | 25 | #[derive(Clone)] 26 | pub struct PathToSlate(pub PathBuf); 27 | 28 | impl SlatePutter for PathToSlate { 29 | fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> { 30 | // For testing (output raw slate data for reference) 31 | /*{ 32 | let mut raw_path = self.0.clone(); 33 | raw_path.set_extension("raw"); 34 | let mut raw_slate = File::create(&raw_path)?; 35 | raw_slate.write_all(&format!("{:?}", slate).as_bytes())?; 36 | raw_slate.sync_all()?; 37 | }*/ 38 | let mut pub_tx = File::create(&self.0)?; 39 | // TODO: 40 | let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; 41 | if as_bin { 42 | let bin_slate = 43 | VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlateSer)?; 44 | pub_tx.write_all(&byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlateSer)?)?; 45 | } else { 46 | pub_tx.write_all( 47 | serde_json::to_string_pretty(&out_slate) 48 | .map_err(|_| ErrorKind::SlateSer)? 49 | .as_bytes(), 50 | )?; 51 | } 52 | pub_tx.sync_all()?; 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl SlateGetter for PathToSlate { 58 | fn get_tx(&self) -> Result<(Slate, bool), Error> { 59 | // try as bin first, then as json 60 | let mut pub_tx_f = File::open(&self.0)?; 61 | let mut data = Vec::new(); 62 | pub_tx_f.read_to_end(&mut data)?; 63 | let bin_res = byte_ser::from_bytes::(&data); 64 | if let Err(e) = bin_res { 65 | debug!("Not a valid binary slate: {} - Will try JSON", e); 66 | } else { 67 | if let Ok(s) = bin_res { 68 | return Ok((Slate::upgrade(s.into())?, true)); 69 | } 70 | } 71 | 72 | // Otherwise try json 73 | let content = String::from_utf8(data).map_err(|_| ErrorKind::SlateSer)?; 74 | Ok((Slate::deserialize_upgrade(&content)?, false)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /impls/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Concrete implementations of types found in libwallet, organised this 16 | //! way mostly to avoid any circular dependencies of any kind 17 | //! Functions in this crate should not use the wallet api crate directly 18 | 19 | use blake2_rfc as blake2; 20 | 21 | #[macro_use] 22 | extern crate serde_derive; 23 | #[macro_use] 24 | extern crate log; 25 | #[macro_use] 26 | extern crate serde_json; 27 | use grin_wallet_libwallet as libwallet; 28 | use grin_wallet_util::grin_api as api; 29 | use grin_wallet_util::grin_chain as chain; 30 | use grin_wallet_util::grin_core as core; 31 | use grin_wallet_util::grin_keychain as keychain; 32 | use grin_wallet_util::grin_store as store; 33 | use grin_wallet_util::grin_util as util; 34 | 35 | use grin_wallet_config as config; 36 | 37 | mod adapters; 38 | mod backends; 39 | mod client_utils; 40 | mod error; 41 | mod lifecycle; 42 | mod node_clients; 43 | pub mod test_framework; 44 | pub mod tor; 45 | 46 | pub use crate::adapters::{ 47 | create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, 48 | PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender, 49 | }; 50 | pub use crate::backends::{wallet_db_exists, LMDBBackend}; 51 | pub use crate::error::{Error, ErrorKind}; 52 | pub use crate::lifecycle::DefaultLCProvider; 53 | pub use crate::node_clients::HTTPNodeClient; 54 | 55 | use crate::keychain::{ExtKeychain, Keychain}; 56 | 57 | use libwallet::{NodeClient, WalletInst, WalletLCProvider}; 58 | 59 | /// Main wallet instance 60 | pub struct DefaultWalletImpl<'a, C> 61 | where 62 | C: NodeClient + 'a, 63 | { 64 | lc_provider: DefaultLCProvider<'a, C, ExtKeychain>, 65 | } 66 | 67 | impl<'a, C> DefaultWalletImpl<'a, C> 68 | where 69 | C: NodeClient + 'a, 70 | { 71 | pub fn new(node_client: C) -> Result { 72 | let lc_provider = DefaultLCProvider::new(node_client); 73 | Ok(DefaultWalletImpl { 74 | lc_provider: lc_provider, 75 | }) 76 | } 77 | } 78 | 79 | impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<'a, C> 80 | where 81 | DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>, 82 | L: WalletLCProvider<'a, C, K>, 83 | C: NodeClient + 'a, 84 | K: Keychain + 'a, 85 | { 86 | fn lc_provider( 87 | &mut self, 88 | ) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), libwallet::Error> { 89 | Ok(&mut self.lc_provider) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.ci/windows-release.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: 'cargo test --all' 3 | displayName: Cargo Test All 4 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 5 | - script: 'cargo clean' 6 | displayName: Cargo Clean 7 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 8 | - script: 'cargo build --release' 9 | displayName: Build Release 10 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 11 | - script: | 12 | SET MY_TAG=$(Build.SourceBranch) 13 | SET MY_TAG=%MY_TAG:~10% 14 | echo %MY_TAG% 15 | echo %PLATFORM% 16 | echo ##vso[task.setvariable variable=build.my_tag]%MY_TAG% 17 | echo ##vso[task.setvariable variable=build.platform]%PLATFORM% 18 | displayName: "Create my tag variable" 19 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 20 | - task: CopyFiles@2 21 | displayName: Copy assets 22 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 23 | inputs: 24 | sourceFolder: '$(Build.SourcesDirectory)\target\release' 25 | contents: 'grin-wallet.exe' 26 | targetFolder: '$(Build.BinariesDirectory)\grin-wallet' 27 | - task: ArchiveFiles@2 28 | displayName: Gather assets 29 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 30 | inputs: 31 | rootFolderOrFile: '$(Build.BinariesDirectory)\grin-wallet' 32 | archiveType: 'zip' 33 | archiveFile: '$(Build.ArtifactStagingDirectory)\grin-wallet-$(build.my_tag)-$(build.platform).zip' 34 | - script: | 35 | powershell -Command "cd $(Build.ArtifactStagingDirectory); get-filehash -algorithm sha256 grin-wallet-$(build.my_tag)-$(build.platform).zip | Format-List |  Out-String | ForEach-Object { $_.Trim() } > grin-wallet-$(build.my_tag)-$(build.platform)-sha256sum.txt" 36 | displayName: Create Checksum 37 | condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 38 | - task: GithubRelease@0 39 | displayName: Github release 40 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) 41 | inputs: 42 | gitHubConnection: 'ignopeverell' 43 | repositoryName: 'mimblewimble/grin-wallet' 44 | action: 'edit' 45 | target: '$(build.sourceVersion)' 46 | tagSource: 'manual' 47 | tag: '$(build.my_tag)' 48 | assets: | 49 | $(Build.ArtifactStagingDirectory)\grin-wallet-$(build.my_tag)-$(build.platform).zip 50 | $(Build.ArtifactStagingDirectory)\grin-wallet-$(build.my_tag)-$(build.platform)-sha256sum.txt 51 | title: '$(build.my_tag)' 52 | assetUploadMode: 'replace' 53 | addChangeLog: true 54 | -------------------------------------------------------------------------------- /libwallet/tests/slates/v2.slate: -------------------------------------------------------------------------------- 1 | { 2 | "version_info": { 3 | "version": 2, 4 | "orig_version": 2, 5 | "block_header_version": 1 6 | }, 7 | "num_participants": 2, 8 | "id": "e0c69803-db50-40d9-a968-496e86660cd4", 9 | "tx": { 10 | "offset": "a853afebf15d8c111f654059940945b4782c38660397257707b53ebfdb403a52", 11 | "body": { 12 | "inputs": [ 13 | { 14 | "features": "Plain", 15 | "commit": "09d304aed6300f8124eb8b2d46cc1e0a7b7a9b9042b9cb35e020dd9552df9c697c" 16 | }, 17 | { 18 | "features": "Plain", 19 | "commit": "09d3cc915dc317485dc8bbf5ec4669a40bb9d3300c96df3384d116ddad498d0db1" 20 | } 21 | ], 22 | "outputs": [ 23 | { 24 | "features": "Plain", 25 | "commit": "08d3453eb5ce35a1b6bbc2a7a9afe32483774c011f9975f42393468fa5cd4349a7", 26 | "proof": "db206834c022eec1f346a67b571941f1b6867ae4bd8189ca064b690b32367e454a4a5add51761c472b0e0994ce7f00578bc06ae7b9afdf8ce2118546771976d900464214d3b831fe74a94876980a928315afb5c2af018f5d595e56fd740658b0c4f2d4f463e401cbec2704b31005cd8d7d87458290a3668cc2e82c2b0867d991072544f9e8c805056c97ff66cc052cf2a9666768d0d68acdc6ea1fc80fb9b5e6e19366c7b49ada38b368c0c3e3f73977df003f0c6744737b31b058c7d4e2766e97ee04147ef04be22906f087842205813c7d817598c689c840087d35cc9ce9a98f52e68c66bdde0521acf814737efd072654728f418e6494a7eb7fa6305ec7d572abb91d3bfabf7215e77e0c9cf33769572ff9a8671a24e0a04302e6ac5cee9928ec11d7c9861ed18718142a1563967955e428e4134c6dde88bdbea11248ae99d784a56592a065122948b2c2fb8be25c119345b9fa7db2efbdfcf846e9ba47efff3d0024bdb998e93bcabe1a00222ba36b88ec4f7c2a2151bf00b225f6a14b4de66658daecaa219813f51a9239eec961c6713106b64c4f1ff851e54795220ee3cdc59531f0acc050e17c848b21b916b571b2f6b093fccec046587d0a1718c82bd7a78e22223fe1484dec841820139950dce84c97659b0eac1bfa5fce85d5602f480d714dcab1459c4f29e2746bccb4494d800935ddc630f53257649f1544702003a583d55422e957192faebffcb8d883ec6bb2132c86249d6b50edae84f3c06842b2714267249c8df58e2edc3aca69dff66ee32fb5d93db9156df373ab51df2c094742517b46ff95298caec3464151ea91c8a8fe74bb60ffb94c7c974aa6cb2e47dd1ee05f471e2d2f0b555efe17302769139760bc110c979453f7bfab43b3f3cba4d94c8a5eeb58264bb5c16de6acbbc9c56cb069e7e1ac1f7838d0a6424017b8d563" 27 | } 28 | ], 29 | "kernels": [ 30 | { 31 | "features": "HeightLocked", 32 | "fee": "7000000", 33 | "lock_height": "70194", 34 | "excess": "000000000000000000000000000000000000000000000000000000000000000000", 35 | "excess_sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 36 | } 37 | ] 38 | } 39 | }, 40 | "amount": "84825921007", 41 | "fee": "7000000", 42 | "height": "70194", 43 | "lock_height": "70194", 44 | "participant_data": [ 45 | { 46 | "id": "0", 47 | "public_blind_excess": "0391f8fc74bb5ff4de373352e7dee00860d4fb78ed7a99765585af980d8a31c615", 48 | "public_nonce": "0206562c21a7f3a003622722ee93c4ecbbecead4a6ad8ee5d930b51ca4a6ca6d01", 49 | "part_sig": null, 50 | "message": null, 51 | "message_sig": null 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /libwallet/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Higher level wallet functions which can be used by callers to operate 16 | //! on the wallet, as well as helpers to invoke and instantiate wallets 17 | //! and listeners 18 | 19 | #![deny(non_upper_case_globals)] 20 | #![deny(non_camel_case_types)] 21 | #![deny(non_snake_case)] 22 | #![deny(unused_mut)] 23 | #![warn(missing_docs)] 24 | 25 | use grin_wallet_config as config; 26 | use grin_wallet_util::grin_core; 27 | use grin_wallet_util::grin_keychain; 28 | use grin_wallet_util::grin_store; 29 | use grin_wallet_util::grin_util; 30 | 31 | use grin_wallet_util as util; 32 | 33 | use blake2_rfc as blake2; 34 | 35 | use failure; 36 | extern crate failure_derive; 37 | 38 | #[macro_use] 39 | extern crate serde_derive; 40 | #[macro_use] 41 | extern crate log; 42 | #[macro_use] 43 | extern crate lazy_static; 44 | 45 | extern crate strum; 46 | #[macro_use] 47 | extern crate strum_macros; 48 | 49 | pub mod address; 50 | pub mod api_impl; 51 | mod error; 52 | mod internal; 53 | mod slate; 54 | pub mod slate_versions; 55 | pub mod slatepack; 56 | mod types; 57 | 58 | pub use crate::error::{Error, ErrorKind}; 59 | pub use crate::slate::{ParticipantData, Slate, SlateState}; 60 | pub use crate::slate_versions::v4::sig_is_blank; 61 | pub use crate::slate_versions::{ 62 | SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION, 63 | GRIN_BLOCK_HEADER_VERSION, 64 | }; 65 | pub use crate::slatepack::{ 66 | Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, Slatepacker, SlatepackerArgs, 67 | }; 68 | pub use api_impl::owner_updater::StatusMessage; 69 | pub use api_impl::types::{ 70 | BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult, 71 | OutputCommitMapping, PaymentProof, VersionInfo, 72 | }; 73 | pub use api_impl::types::{IssueTokenArgs, TokenOutputCommitMapping}; 74 | pub use internal::scan::scan; 75 | pub use internal::token_scan::token_scan; 76 | pub use slate_versions::ser as dalek_ser; 77 | pub use types::{ 78 | AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData, 79 | OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper, 80 | WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch, 81 | }; 82 | pub use types::{TokenOutputData, TokenTxLogEntry, TokenTxLogEntryType}; 83 | 84 | /// Helper for taking a lock on the wallet instance 85 | #[macro_export] 86 | macro_rules! wallet_lock { 87 | ($wallet_inst: expr, $wallet: ident) => { 88 | let inst = $wallet_inst.clone(); 89 | let mut w_lock = inst.lock(); 90 | let w_provider = w_lock.lc_provider()?; 91 | let $wallet = w_provider.wallet_inst()?; 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /doc/design/wallet-arch.puml: -------------------------------------------------------------------------------- 1 | @startuml grin-wallet-overview 2 | skinparam componentStyle uml2 3 | 4 | [Grin Node] as grin_node 5 | 6 | folder "Provided by Grin" as services { 7 | component foreign_api [ 8 | **Foreign API** 9 | External-Facing functions 10 | - receive_tx, build coinbase 11 | ] 12 | 13 | component owner_api [ 14 | **Owner API** 15 | Functions used by wallet owner only 16 | - retrieve outputs, retrieve txs, 17 | get balances, send, etc. . . 18 | 19 | ] 20 | component libtx [ 21 | **Transaction Library (libTx)** 22 | Lower-Level transaction functions 23 | - Build transaction (via Slate), sign, 24 | build reward, fees, etc. . . 25 | ] 26 | component libwallet [ 27 | **Wallet Library (libWallet) ** 28 | - Higher level wallet functions (select coins, 29 | update wallet from node, etc) 30 | - Service Controller 31 | (instantiate libs, start listeners) 32 | ] 33 | () "Owner HTTP Listener (localhost only)" as owner_http 34 | () "Foreign HTTP Listener" as foreign_http 35 | () "Owner Single-Use" as owner_single 36 | () "Foreign Single-Use" as foreign_single 37 | } 38 | 39 | ' Trait definitions 40 | package "Traits Implemented by Wallets" as traits { 41 | database "WalletBackend" as wallet_backend 42 | database "KeyChain" as keychain 43 | component "NodeClient" as wallet_client 44 | } 45 | 46 | note left of wallet_client 47 | - Communication layer implementation 48 | - Handles underlying communication with grin node 49 | or other wallets 50 | - HTTP implementation provided currently, (Other, 51 | more secure protocols possible.) 52 | end note 53 | 54 | note bottom of keychain 55 | - Handles all key derivation operations 56 | end note 57 | 58 | note bottom of wallet_backend 59 | - Implements underlying storage for wallet data 60 | - LMDB storage provided in default client, others 61 | possible (Flat-file, other DBs, etc) 62 | end note 63 | 64 | libtx <--> traits 65 | libwallet <--> traits 66 | 67 | note right of traits 68 | **Default Wallet simply a struct that provides** 69 | **implementations for these 3 traits** 70 | end note 71 | 72 | ' Client Side 73 | 'package "Provided as reference implementation" { 74 | [Pure JS Wallet Client Implementation] as js_client 75 | [Command Line Wallet Client] as cl_client 76 | component web_server [ 77 | V. Light Rust Web Server - Serve static files (TBD) 78 | (Provided by default - localhost only) 79 | (Serve up pure JS client) 80 | ] 81 | '} 82 | 83 | [External Wallets] as external_wallets 84 | [External Wallets] as external_wallets_2 85 | 86 | wallet_client <--> grin_node 87 | wallet_client <--> external_wallets_2 88 | 89 | web_server <--> owner_http 90 | js_client <-- web_server 91 | cl_client <--> owner_single 92 | cl_client <--> foreign_single 93 | 94 | owner_single <--> owner_api 95 | foreign_single <--> foreign_api 96 | 97 | libwallet <--> libtx 98 | 99 | foreign_api --> libwallet 100 | owner_api --> libwallet 101 | 102 | js_client <--> owner_http 103 | owner_http <--> owner_api 104 | external_wallets <--> foreign_http 105 | foreign_http <--> foreign_api 106 | 107 | 'layout fix 108 | 'grin_node -[hidden]- wallet_backend 109 | 110 | @enduml -------------------------------------------------------------------------------- /libwallet/tests/slate_versioning.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! core::libtx specific tests 15 | //use grin_wallet_libwallet::Slate; 16 | 17 | // test all slate conversions 18 | /* TODO: Turn back on upon release of new slate version 19 | #[test] 20 | fn slate_conversions() { 21 | // Test V0 to V2 22 | let v0 = include_str!("slates/v0.slate"); 23 | let res = Slate::deserialize_upgrade(&v0); 24 | assert!(res.is_ok()); 25 | // should serialize as latest 26 | let mut res = res.unwrap(); 27 | assert_eq!(res.version_info.orig_version, 0); 28 | res.version_info.orig_version = 2; 29 | let s = serde_json::to_string(&res); 30 | assert!(s.is_ok()); 31 | let s = s.unwrap(); 32 | let v = Slate::parse_slate_version(&s); 33 | assert!(v.is_ok()); 34 | assert_eq!(v.unwrap(), 2); 35 | println!("v0 -> v2: {}", s); 36 | 37 | // Test V1 to V2 38 | let v1 = include_str!("slates/v1.slate"); 39 | let res = Slate::deserialize_upgrade(&v1); 40 | assert!(res.is_ok()); 41 | // should serialize as latest 42 | let mut res = res.unwrap(); 43 | assert_eq!(res.version_info.orig_version, 1); 44 | res.version_info.orig_version = 2; 45 | let s = serde_json::to_string(&res); 46 | assert!(s.is_ok()); 47 | let s = s.unwrap(); 48 | let v = Slate::parse_slate_version(&s); 49 | assert!(v.is_ok()); 50 | assert_eq!(v.unwrap(), 2); 51 | println!("v1 -> v2: {}", s); 52 | 53 | // V2 -> V2, check version 54 | let v2 = include_str!("slates/v2.slate"); 55 | let res = Slate::deserialize_upgrade(&v2); 56 | assert!(res.is_ok()); 57 | let res = res.unwrap(); 58 | assert_eq!(res.version_info.orig_version, 2); 59 | let s = serde_json::to_string(&res); 60 | assert!(s.is_ok()); 61 | let s = s.unwrap(); 62 | let v = Slate::parse_slate_version(&s); 63 | assert!(v.is_ok()); 64 | assert_eq!(v.unwrap(), 2); 65 | 66 | // Downgrade to V1 67 | let v2 = include_str!("slates/v2.slate"); 68 | let res = Slate::deserialize_upgrade(&v2); 69 | assert!(res.is_ok()); 70 | let mut res = res.unwrap(); 71 | // downgrade 72 | res.version_info.orig_version = 1; 73 | let s = serde_json::to_string(&res); 74 | assert!(s.is_ok()); 75 | let s = s.unwrap(); 76 | let v = Slate::parse_slate_version(&s); 77 | assert!(v.is_ok()); 78 | assert_eq!(v.unwrap(), 1); 79 | println!("v2 -> v1: {}", s); 80 | 81 | // Downgrade to V0 82 | let v2 = include_str!("slates/v2.slate"); 83 | let res = Slate::deserialize_upgrade(&v2); 84 | assert!(res.is_ok()); 85 | let mut res = res.unwrap(); 86 | // downgrade 87 | res.version_info.orig_version = 0; 88 | let s = serde_json::to_string(&res); 89 | assert!(s.is_ok()); 90 | let s = s.unwrap(); 91 | let v = Slate::parse_slate_version(&s); 92 | assert!(v.is_ok()); 93 | assert_eq!(v.unwrap(), 0); 94 | println!("v2 -> v0: {}", s); 95 | } 96 | */ 97 | -------------------------------------------------------------------------------- /tests/tor_dev_helper.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | #[macro_use] 15 | extern crate clap; 16 | 17 | #[macro_use] 18 | extern crate log; 19 | 20 | extern crate vcash_wallet; 21 | 22 | use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; 23 | 24 | use clap::App; 25 | use std::thread; 26 | use std::time::Duration; 27 | 28 | use grin_wallet_impls::DefaultLCProvider; 29 | use grin_wallet_util::grin_keychain::ExtKeychain; 30 | 31 | use grin_wallet_util::grin_util as util; 32 | 33 | #[macro_use] 34 | mod common; 35 | use common::{execute_command, initial_setup_wallet, instantiate_wallet, setup_global_chain_type}; 36 | 37 | // Development testing helper for tor/socks investigation. 38 | // Not (yet) to be run as part of automated testing 39 | 40 | fn setup_no_clean() { 41 | util::init_test_logger(); 42 | setup_global_chain_type(); 43 | } 44 | 45 | #[ignore] 46 | #[test] 47 | fn socks_tor() -> Result<(), grin_wallet_controller::Error> { 48 | let test_dir = "target/test_output/socks_tor"; 49 | let yml = load_yaml!("../src/bin/grin-wallet.yml"); 50 | let app = App::from_yaml(yml); 51 | setup_no_clean(); 52 | 53 | setup_proxy!(test_dir, chain, wallet1, client1, mask1, wallet2, client2, _mask2); 54 | 55 | // Tor should be running at this point for wallet 2, with a hidden service 56 | // bound to the listening port 53415. By default, tor will also be running 57 | // a socks proxy lister at 127.0.0.1 9050 (both wallets can use for now) 58 | // 59 | // Relevant torrc config: 60 | // HiddenServiceDir ./hidden_service/ 61 | // HiddenServicePort 80 127.0.0.1:53415 62 | // 63 | // tor -f torrc 64 | 65 | // Substitute whatever onion address has been created 66 | let onion_address = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"; 67 | 68 | // run the foreign listener for wallet 2 69 | let arg_vec = vec!["grin-wallet", "-p", "password", "listen"]; 70 | // Set owner listener running 71 | thread::spawn(move || { 72 | let yml = load_yaml!("../src/bin/grin-wallet.yml"); 73 | let app = App::from_yaml(yml); 74 | execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone()).unwrap(); 75 | }); 76 | 77 | // dumb pause for now, hidden service should already be running 78 | thread::sleep(Duration::from_millis(3000)); 79 | 80 | // mine into wallet 1 a bit 81 | let bh = 5u64; 82 | let _ = 83 | test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); 84 | 85 | // now, test send from wallet 1 over tor 86 | let arg_vec = vec![ 87 | "grin-wallet", 88 | "-p", 89 | "password", 90 | "send", 91 | "-c", 92 | "2", 93 | "-d", 94 | onion_address, 95 | "10", 96 | ]; 97 | execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; 98 | 99 | Ok(()) 100 | } 101 | -------------------------------------------------------------------------------- /doc/tls-setup.md: -------------------------------------------------------------------------------- 1 | # Wallet TLS setup 2 | 3 | ## What you need 4 | * A server with a static IP address (eg `3.3.3.3`) 5 | * A domain name ownership (`example.com`) 6 | * DNS configuration for this IP (`grin1.example.com` -> `3.3.3.3`) 7 | 8 | If you don't have a static IP you may want to consider using services like DynDNS which support dynamic IP resolving, this case is not covered by this guide, but all the next steps are equally applicable. 9 | 10 | If you don't have a domain name there is a possibility to get a TLS certificate for your IP, but you have to pay for that (so perhaps it's cheaper to buy a domain name) and it's rarely supported by certificate providers. 11 | 12 | ## I have a TLS certificate already 13 | Uncomment and update the following lines in wallet config (by default `~/.vcash/grin-wallet.toml`): 14 | 15 | ```toml 16 | tls_certificate_file = "/path/to/my/cerificate/fullchain.pem" 17 | tls_certificate_key = "/path/to/my/cerificate/privkey.pem" 18 | ``` 19 | 20 | If you have Stratum server enabled (you run a miner) make sure that wallet listener URL starts with `https` in node config (by default `~/.vcash/vcash-server.toml`): 21 | 22 | ```toml 23 | wallet_listener_url = "https://grin1.example.com:13515" 24 | ``` 25 | 26 | Make sure your user has read access to the files (see below for how to do it). Restart wallet. If you changed your node configuration restart `grin` too. When you (or someone else) send grins to this wallet the destination (`-d` option) must start with `https://`, not with `http://`. 27 | 28 | ## I don't have a TLS certificate 29 | You can get it for free from [Let's Encrypt](https://letsencrypt.org/). To simplify the process we need `certbot`. 30 | 31 | ### Install certbot 32 | Go to [Certbot home page](https://certbot.eff.org/), choose I'm using `None of the above` and your OS (eg `Ubuntu 18.04` which will be used as an example). You will be redirected to a page with instructions like [steps for Ubuntu](https://certbot.eff.org/lets-encrypt/ubuntubionic-other). Follow instructions from `Install` section. As result you should have `certbot` installed. 33 | 34 | ### Obtain certificate 35 | If you have experince with `certboot` feel free to use any type of challenge. This guide covers the simplest case of HTTP challenge. For this you need to have a web server listening on port `80`, which requires running it as root in the simplest case. We will use the server provided by certbot. **Make sure you have port 80 open** 36 | 37 | ```sh 38 | sudo certbot certonly --standalone -d grin1.example.com 39 | ``` 40 | 41 | It will ask you some questions, as result you should see something like: 42 | 43 | ``` 44 | Congratulations! Your certificate and chain have been saved at: 45 | /etc/letsencrypt/live/grin1.example.com/fullchain.pem 46 | Your key file has been saved at: 47 | /etc/letsencrypt/live/grin1.example.com/privkey.pem 48 | Your cert will expire on 2019-01-16. To obtain a new or tweaked 49 | version of this certificate in the future, simply run certbot 50 | again. To non-interactively renew *all* of your certificates, run 51 | "certbot renew" 52 | ``` 53 | 54 | ### Change permissions 55 | Now you have the certificate files but only root user can read it. We run grin as `ubuntu` user. There are different scenarios how to fix it, the simplest one is to create a group which will have access to `/etc/letsencrypt` directory and add our user to this group. 56 | 57 | ```sh 58 | sudo groupadd tls-cert 59 | sudo usermod -a -G tls-cert ubuntu 60 | chgrp -R tls-cert /etc/letsencrypt 61 | chmod -R g=rX /etc/letsencrypt 62 | sudo chmod 2755 /etc/letsencrypt 63 | ``` 64 | 65 | The last step is needed for renewal, it makes sure that all new files will have the same group ownership. 66 | 67 | ### Update wallet config 68 | Refer to `I have a TLS certificate already` because you have it now. Use the folowing values: 69 | 70 | ```toml 71 | tls_certificate_file = "/etc/letsencrypt/live/grin1.example.com/fullchain.pem" 72 | tls_certificate_key = "/etc/letsencrypt/live/grin1.example.com/privkey.pem" 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /doc/samples/v3_api_node/src/index.js: -------------------------------------------------------------------------------- 1 | /* Sample Code for connecting to the V3 Secure API via Node 2 | * 3 | * With thanks to xiaojay of Niffler Wallet: 4 | * https://github.com/grinfans/Niffler/blob/gw3/src/shared/walletv3.js 5 | * 6 | */ 7 | 8 | const jayson = require('jayson/promise'); 9 | const crypto = require('crypto'); 10 | 11 | const client = jayson.client.http('http://localhost:3420/v3/owner'); 12 | 13 | // Demo implementation of using `aes-256-gcm` with node.js's `crypto` lib. 14 | const aes256gcm = (shared_secret) => { 15 | const ALGO = 'aes-256-gcm'; 16 | 17 | // encrypt returns base64-encoded ciphertext 18 | const encrypt = (str, nonce) => { 19 | let key = Buffer.from(shared_secret, 'hex') 20 | const cipher = crypto.createCipheriv(ALGO, key, nonce) 21 | const enc = Buffer.concat([cipher.update(str, 'utf8'), cipher.final()]) 22 | const tag = cipher.getAuthTag() 23 | return Buffer.concat([enc, tag]).toString('base64') 24 | }; 25 | 26 | // decrypt decodes base64-encoded ciphertext into a utf8-encoded string 27 | const decrypt = (enc, nonce) => { 28 | //key,nonce is all buffer type; data is base64-encoded string 29 | let key = Buffer.from(shared_secret, 'hex') 30 | const data_ = Buffer.from(enc, 'base64') 31 | const decipher = crypto.createDecipheriv(ALGO, key, nonce) 32 | const len = data_.length 33 | const tag = data_.slice(len-16, len) 34 | const text = data_.slice(0, len-16) 35 | decipher.setAuthTag(tag) 36 | const dec = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8'); 37 | return dec 38 | }; 39 | 40 | return { 41 | encrypt, 42 | decrypt, 43 | }; 44 | }; 45 | 46 | class JSONRequestEncrypted { 47 | constructor(id, method, params) { 48 | this.jsonrpc = '2.0' 49 | this.method = method 50 | this.id = id 51 | this.params = params 52 | } 53 | 54 | async send(key){ 55 | const aesCipher = aes256gcm(key); 56 | const nonce = new Buffer.from(crypto.randomBytes(12)); 57 | let enc = aesCipher.encrypt(JSON.stringify(this), nonce); 58 | console.log("Encrypted: " + enc) 59 | let params = { 60 | 'nonce': nonce.toString('hex'), 61 | 'body_enc': enc, 62 | } 63 | let response = await client.request('encrypted_request_v3', params); 64 | 65 | if (response.err) { 66 | throw response.err 67 | } 68 | 69 | const nonce2 = Buffer.from(response.result.Ok.nonce, 'hex'); 70 | const data = Buffer.from(response.result.Ok.body_enc, 'base64'); 71 | 72 | let dec = aesCipher.decrypt(data, nonce2) 73 | return dec 74 | } 75 | } 76 | 77 | async function initSecure() { 78 | let ecdh = crypto.createECDH('secp256k1') 79 | ecdh.generateKeys() 80 | let publicKey = ecdh.getPublicKey('hex', 'compressed') 81 | const params = { 82 | 'ecdh_pubkey': publicKey 83 | } 84 | let response = await client.request('init_secure_api', params); 85 | if (response.err) { 86 | throw response.err 87 | } 88 | 89 | return ecdh.computeSecret(response.result.Ok, 'hex', 'hex') 90 | } 91 | 92 | function sleep(ms) { 93 | return new Promise(resolve => setTimeout(resolve, ms)); 94 | } 95 | 96 | async function main() { 97 | let shared_key = await initSecure(); 98 | 99 | let response = await new JSONRequestEncrypted(1, 'open_wallet', { 100 | "name": null, 101 | "password": "", 102 | }).send(shared_key); 103 | 104 | let token = JSON.parse(response).result.Ok; 105 | 106 | let iterations = 1; 107 | 108 | for (i=0; i Result<(), libwallet::Error> { 36 | // Create a new proxy to simulate server and wallet responses 37 | let mut wallet_proxy = create_wallet_proxy(test_dir); 38 | let chain = wallet_proxy.chain.clone(); 39 | let stopper = wallet_proxy.running.clone(); 40 | 41 | // Create a new wallet test client, and set its queues to communicate with the 42 | // proxy 43 | create_wallet_and_add!( 44 | client1, 45 | wallet1, 46 | mask1_i, 47 | test_dir, 48 | "wallet1", 49 | None, 50 | &mut wallet_proxy, 51 | false 52 | ); 53 | let mask1 = (&mask1_i).as_ref(); 54 | create_wallet_and_add!( 55 | client2, 56 | wallet2, 57 | mask2_i, 58 | test_dir, 59 | "wallet2", 60 | None, 61 | &mut wallet_proxy, 62 | false 63 | ); 64 | let mask2 = (&mask2_i).as_ref(); 65 | 66 | // Set the wallet proxy listener running 67 | thread::spawn(move || { 68 | if let Err(e) = wallet_proxy.run() { 69 | error!("Wallet Proxy error: {}", e); 70 | } 71 | }); 72 | 73 | // add some accounts 74 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 75 | api.create_account_path(m, "mining")?; 76 | api.create_account_path(m, "listener")?; 77 | Ok(()) 78 | })?; 79 | 80 | // add some accounts 81 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { 82 | api.create_account_path(m, "account1")?; 83 | api.create_account_path(m, "account2")?; 84 | Ok(()) 85 | })?; 86 | 87 | // Get some mining done 88 | { 89 | wallet_inst!(wallet1, w); 90 | w.set_parent_key_id_by_name("mining")?; 91 | } 92 | let bh = 10u64; 93 | let _ = 94 | test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); 95 | 96 | let owner_api = api::Owner::new(wallet1, None); 97 | owner_api.start_updater(mask1, Duration::from_secs(5))?; 98 | 99 | // let updater thread run a bit 100 | thread::sleep(Duration::from_secs(10)); 101 | 102 | let messages = owner_api.get_updater_messages(1000)?; 103 | assert_eq!(messages.len(), 36); 104 | 105 | owner_api.stop_updater()?; 106 | stopper.store(false, Ordering::Relaxed); 107 | thread::sleep(Duration::from_secs(2)); 108 | Ok(()) 109 | } 110 | 111 | #[test] 112 | fn updater_thread() { 113 | // The "updater" kicks off a new thread so we need to ensure the global chain_type 114 | // is set for this to work correctly. 115 | setup_global_chain_type(); 116 | 117 | let test_dir = "test_output/updater_thread"; 118 | setup(test_dir); 119 | if let Err(e) = updater_thread_test_impl(test_dir) { 120 | panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 121 | } 122 | clean_output_dir(test_dir); 123 | } 124 | -------------------------------------------------------------------------------- /doc/design/goals.md: -------------------------------------------------------------------------------- 1 | 2 | Mode of Interactions 3 | ==================== 4 | 5 | There's a variety of ways wallet software can be integrated with, from hardware 6 | to automated bots to the more classic desktop wallets. No single implementation 7 | can hope to accommodate all possible interactions, especially if it wants to 8 | remain user friendly (who or whatever the user may be). With that in mind, Grin 9 | needs to provide a healthy base for a more complete wallet ecosystem to 10 | develop. 11 | 12 | We propose to achieve this by implementing, as part of the "standard" wallet: 13 | 14 | * A good set of APIs that are flexible enough for most cases. 15 | * One or two default main mode of interaction. 16 | 17 | While not being exhaustive, the different ways we can imagine wallet software 18 | working with Grin are the following: 19 | 20 | 1. A receive-only online wallet server. This should have some well-known network 21 | address that can be reached by a client. There should be a spending key kept 22 | offline. 23 | 1. A fully offline interaction. The sender uses her wallet to dump a file that's 24 | sent to the receiver in any practical way. The receiver builds upon that file, 25 | sending it back to the sender. The sender finalizes the transaction and sends it 26 | to a Grin node. 27 | 1. Fully online interaction through a non-trusted 3rd party. In this mode 28 | receiver and sender both connect to a web server that facilitates the 29 | interaction. Exchanges can be all be encrypted. 30 | 1. Hardware wallet. Similar to offline but the hardware wallet interacts with 31 | a computer to produce required public keys and signatures. 32 | 1. Web wallet. A 3rd party runs the required software behind the scenes and 33 | handles some of the key generation. This could be done in a custodial, 34 | non-custodial and multisig fashion. 35 | 1. Fully programmatic. Similar to the online server, but both for receiving and 36 | sending, most likely by an automated bot of some sorts. 37 | 38 | As part of the Grin project, we will only consider the first 2 modes of 39 | interaction. We hope that other projects and businesses will tackle other modes 40 | and perhaps even create new ones we haven't considered. 41 | 42 | Design Considerations 43 | ===================== 44 | 45 | Lower-level APIs 46 | ---------------- 47 | 48 | Rust can easily be [reused by other languages](https://doc.rust-lang.org/1.2.0/book/rust-inside-other-languages.html) 49 | like Ruby, Python or node.js, using standard FFI libraries. By providing APIs 50 | to build and manipulate commitments, related bulletproofs and aggregate 51 | signatures we can kill many birds with one stone: 52 | 53 | * Make the job of wallet implementers easier. The underlying cryptographic 54 | concepts can be quite complex. 55 | * Make wallet implementations more secure. As we provide a higher level API, 56 | there is less risk in misusing lower-level constructs. 57 | * Provide some standardization in the way aggregations are done. There are 58 | sometimes multiple ways to build a commitment or aggregate signatures or proofs 59 | in a multiparty output. 60 | * Provide more eyeballs and more security to the standard library. We need to 61 | have the wallet APIs thoroughly reviewed regardless. 62 | 63 | Receive-only Online Wallet 64 | -------------------------- 65 | 66 | To be receive only we need an aggregation between a "hot" receiving key and an 67 | offline spending key. To receive, only the receiving key should be required, to 68 | spend both keys are needed. 69 | 70 | This can work by forming a multi-party output (multisig) where only the public 71 | part of the spending key is known to the receiving server. Practically a master 72 | public key that can be derived similarly to Hierarchical Deterministic wallets 73 | would provide the best security and privacy. 74 | 75 | TODO figure out what's needed for the bulletproof. Maybe pre-compute multiple 76 | of them for ranges of receiving amounts (i.e. 1-10 grins, 10-100 grins, etc). 77 | 78 | Offline Wallet 79 | -------------- 80 | 81 | This is likely the simplest to implement, with each interaction dumping its 82 | intermediate values to a file and building off each other. 83 | -------------------------------------------------------------------------------- /libwallet/src/slate_versions/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! This module contains old slate versions and conversions to the newest slate version 16 | //! Used for serialization and deserialization of slates in a backwards compatible way. 17 | //! Versions earlier than V3 are removed for the 4.0.0 release, but versioning code 18 | //! remains for future needs 19 | 20 | use crate::slate::Slate; 21 | use crate::slate_versions::v4::{CoinbaseV4, SlateV4}; 22 | use crate::slate_versions::v4_bin::SlateV4Bin; 23 | use crate::types::CbData; 24 | use crate::Error; 25 | use std::convert::TryFrom; 26 | 27 | pub mod ser; 28 | 29 | #[allow(missing_docs)] 30 | pub mod v4; 31 | #[allow(missing_docs)] 32 | pub mod v4_bin; 33 | 34 | /// The most recent version of the slate 35 | pub const CURRENT_SLATE_VERSION: u16 = 4; 36 | 37 | /// The grin block header this slate is intended to be compatible with 38 | pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; 39 | 40 | /// Existing versions of the slate 41 | #[derive(EnumIter, Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] 42 | pub enum SlateVersion { 43 | /// V4 (most current) 44 | V4, 45 | } 46 | 47 | #[derive(Debug, Serialize, Deserialize)] 48 | #[serde(untagged)] 49 | /// Versions are ordered newest to oldest so serde attempts to 50 | /// deserialize newer versions first, then falls back to older versions. 51 | pub enum VersionedSlate { 52 | /// Current (4.0.0 Onwards ) 53 | V4(SlateV4), 54 | } 55 | 56 | impl VersionedSlate { 57 | /// Return slate version 58 | pub fn version(&self) -> SlateVersion { 59 | match *self { 60 | VersionedSlate::V4(_) => SlateVersion::V4, 61 | } 62 | } 63 | 64 | /// convert this slate type to a specified older version 65 | pub fn into_version(slate: Slate, version: SlateVersion) -> Result { 66 | match version { 67 | SlateVersion::V4 => Ok(VersionedSlate::V4(slate.into())), 68 | } 69 | } 70 | } 71 | 72 | impl From for Slate { 73 | fn from(slate: VersionedSlate) -> Slate { 74 | match slate { 75 | VersionedSlate::V4(s) => Slate::from(s), 76 | } 77 | } 78 | } 79 | 80 | #[derive(Deserialize, Serialize)] 81 | #[serde(untagged)] 82 | /// Binary versions, can only be parsed 1:1 into the appropriate 83 | /// version, and VersionedSlate can up/downgrade from there 84 | pub enum VersionedBinSlate { 85 | /// Version 4, binary 86 | V4(SlateV4Bin), 87 | } 88 | 89 | impl TryFrom for VersionedBinSlate { 90 | type Error = Error; 91 | fn try_from(slate: VersionedSlate) -> Result { 92 | match slate { 93 | VersionedSlate::V4(s) => Ok(VersionedBinSlate::V4(SlateV4Bin(s))), 94 | } 95 | } 96 | } 97 | 98 | impl From for VersionedSlate { 99 | fn from(slate: VersionedBinSlate) -> VersionedSlate { 100 | match slate { 101 | VersionedBinSlate::V4(s) => VersionedSlate::V4(s.0), 102 | } 103 | } 104 | } 105 | 106 | #[derive(Deserialize, Serialize)] 107 | #[serde(untagged)] 108 | /// Versions are ordered newest to oldest so serde attempts to 109 | /// deserialize newer versions first, then falls back to older versions. 110 | pub enum VersionedCoinbase { 111 | /// Current supported coinbase version. 112 | V4(CoinbaseV4), 113 | } 114 | 115 | impl VersionedCoinbase { 116 | /// convert this coinbase data to a specific versioned representation for the json api. 117 | pub fn into_version(cb: CbData, version: SlateVersion) -> VersionedCoinbase { 118 | match version { 119 | SlateVersion::V4 => VersionedCoinbase::V4(cb.into()), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /impls/src/adapters/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod file; 16 | pub mod http; 17 | mod keybase; 18 | mod slatepack; 19 | 20 | pub use self::file::PathToSlate; 21 | pub use self::http::{HttpSlateSender, SchemeNotHttp}; 22 | pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; 23 | pub use self::slatepack::PathToSlatepack; 24 | 25 | use crate::config::{TorConfig, WalletConfig}; 26 | use crate::libwallet::{Error, ErrorKind, Slate}; 27 | use crate::tor::config::complete_tor_address; 28 | use crate::util::ZeroingString; 29 | 30 | /// Sends transactions to a corresponding SlateReceiver 31 | pub trait SlateSender { 32 | /// Send a transaction slate to another listening wallet and return result 33 | /// TODO: Probably need a slate wrapper type 34 | fn send_tx(&mut self, slate: &Slate, finalize: bool) -> Result; 35 | } 36 | 37 | pub trait SlateReceiver { 38 | /// Start a listener, passing received messages to the wallet api directly 39 | /// Takes a wallet config for now to avoid needing all sorts of awkward 40 | /// type parameters on this trait 41 | fn listen( 42 | &self, 43 | config: WalletConfig, 44 | passphrase: ZeroingString, 45 | account: &str, 46 | node_api_secret: Option, 47 | ) -> Result<(), Error>; 48 | } 49 | 50 | /// Posts slates to be read later by a corresponding getter 51 | pub trait SlatePutter { 52 | /// Send a transaction asynchronously 53 | fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error>; 54 | } 55 | 56 | /// Checks for a transaction from a corresponding SlatePutter, returns the transaction if it exists 57 | pub trait SlateGetter { 58 | /// Receive a transaction async. (Actually just read it from wherever and return the slate). 59 | /// Returns (Slate, whether it was in binary form) 60 | fn get_tx(&self) -> Result<(Slate, bool), Error>; 61 | } 62 | 63 | /// select a SlateSender based on method and dest fields from, e.g., SendArgs 64 | pub fn create_sender( 65 | method: &str, 66 | dest: &str, 67 | tor_config: Option, 68 | ) -> Result, Error> { 69 | let invalid = || { 70 | ErrorKind::WalletComms(format!( 71 | "Invalid wallet comm type and destination. method: {}, dest: {}", 72 | method, dest 73 | )) 74 | }; 75 | 76 | let mut method = method; 77 | 78 | // will test if this is a tor address and fill out 79 | // the http://[].onion if missing 80 | let dest = match complete_tor_address(dest) { 81 | Ok(d) => { 82 | method = "tor"; 83 | d 84 | } 85 | Err(_) => dest.into(), 86 | }; 87 | 88 | Ok(match method { 89 | "http" => Box::new(HttpSlateSender::new(&dest).map_err(|_| invalid())?), 90 | "tor" => match tor_config { 91 | None => { 92 | return Err( 93 | ErrorKind::WalletComms("Tor Configuration required".to_string()).into(), 94 | ); 95 | } 96 | Some(tc) => Box::new( 97 | HttpSlateSender::with_socks_proxy(&dest, &tc.socks_proxy_addr, &tc.send_config_dir) 98 | .map_err(|_| invalid())?, 99 | ), 100 | }, 101 | "keybase" => Box::new(KeybaseChannel::new(dest)?), 102 | "self" => { 103 | return Err(ErrorKind::WalletComms( 104 | "No sender implementation for \"self\".".to_string(), 105 | ) 106 | .into()); 107 | } 108 | "file" => { 109 | return Err(ErrorKind::WalletComms( 110 | "File based transactions must be performed asynchronously.".to_string(), 111 | ) 112 | .into()); 113 | } 114 | _ => { 115 | return Err(ErrorKind::WalletComms(format!( 116 | "Wallet comm method \"{}\" does not exist.", 117 | method 118 | )) 119 | .into()); 120 | } 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /libwallet/src/internal/keys.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Wallet key management functions 16 | use crate::error::{Error, ErrorKind}; 17 | use crate::grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain}; 18 | use crate::grin_util::secp::key::SecretKey; 19 | use crate::types::{AcctPathMapping, NodeClient, WalletBackend}; 20 | 21 | /// Get next available key in the wallet for a given parent 22 | pub fn next_available_key<'a, T: ?Sized, C, K>( 23 | wallet: &mut T, 24 | keychain_mask: Option<&SecretKey>, 25 | ) -> Result 26 | where 27 | T: WalletBackend<'a, C, K>, 28 | C: NodeClient + 'a, 29 | K: Keychain + 'a, 30 | { 31 | let child = wallet.next_child(keychain_mask)?; 32 | Ok(child) 33 | } 34 | 35 | /// Retrieve an existing key from a wallet 36 | pub fn retrieve_existing_key<'a, T: ?Sized, C, K>( 37 | wallet: &T, 38 | key_id: Identifier, 39 | mmr_index: Option, 40 | ) -> Result<(Identifier, u32), Error> 41 | where 42 | T: WalletBackend<'a, C, K>, 43 | C: NodeClient + 'a, 44 | K: Keychain + 'a, 45 | { 46 | let existing = wallet.get(&key_id, &mmr_index)?; 47 | let key_id = existing.key_id.clone(); 48 | let derivation = existing.n_child; 49 | Ok((key_id, derivation)) 50 | } 51 | 52 | /// Returns a list of account to BIP32 path mappings 53 | pub fn accounts<'a, T: ?Sized, C, K>(wallet: &mut T) -> Result, Error> 54 | where 55 | T: WalletBackend<'a, C, K>, 56 | C: NodeClient + 'a, 57 | K: Keychain + 'a, 58 | { 59 | Ok(wallet.acct_path_iter().collect()) 60 | } 61 | 62 | /// Adds an new parent account path with a given label 63 | pub fn new_acct_path<'a, T: ?Sized, C, K>( 64 | wallet: &mut T, 65 | keychain_mask: Option<&SecretKey>, 66 | label: &str, 67 | ) -> Result 68 | where 69 | T: WalletBackend<'a, C, K>, 70 | C: NodeClient + 'a, 71 | K: Keychain + 'a, 72 | { 73 | let label = label.to_owned(); 74 | if wallet.acct_path_iter().any(|l| l.label == label) { 75 | return Err(ErrorKind::AccountLabelAlreadyExists(label).into()); 76 | } 77 | 78 | // We're always using paths at m/k/0 for parent keys for output derivations 79 | // so find the highest of those, then increment (to conform with external/internal 80 | // derivation chains in BIP32 spec) 81 | 82 | let highest_entry = wallet.acct_path_iter().max_by(|a, b| { 83 | ::from(a.path.to_path().path[0]).cmp(&::from(b.path.to_path().path[0])) 84 | }); 85 | 86 | let return_id = { 87 | if let Some(e) = highest_entry { 88 | let mut p = e.path.to_path(); 89 | p.path[0] = ChildNumber::from(::from(p.path[0]) + 1); 90 | p.to_identifier() 91 | } else { 92 | ExtKeychain::derive_key_id(2, 0, 0, 0, 0) 93 | } 94 | }; 95 | 96 | let save_path = AcctPathMapping { 97 | label: label, 98 | path: return_id.clone(), 99 | }; 100 | 101 | let mut batch = wallet.batch(keychain_mask)?; 102 | batch.save_acct_path(save_path)?; 103 | batch.commit()?; 104 | Ok(return_id) 105 | } 106 | 107 | /// Adds/sets a particular account path with a given label 108 | pub fn set_acct_path<'a, T: ?Sized, C, K>( 109 | wallet: &mut T, 110 | keychain_mask: Option<&SecretKey>, 111 | label: &str, 112 | path: &Identifier, 113 | ) -> Result<(), Error> 114 | where 115 | T: WalletBackend<'a, C, K>, 116 | C: NodeClient + 'a, 117 | K: Keychain + 'a, 118 | { 119 | let label = label.to_owned(); 120 | let save_path = AcctPathMapping { 121 | label: label, 122 | path: path.clone(), 123 | }; 124 | 125 | let mut batch = wallet.batch(keychain_mask)?; 126 | batch.save_acct_path(save_path)?; 127 | batch.commit()?; 128 | Ok(()) 129 | } 130 | -------------------------------------------------------------------------------- /libwallet/src/slatepack/packer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::convert::TryFrom; 16 | use std::str; 17 | 18 | use super::armor::HEADER; 19 | use crate::{ 20 | slatepack, Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, 21 | VersionedBinSlate, VersionedSlate, 22 | }; 23 | use crate::{Error, ErrorKind}; 24 | 25 | use grin_wallet_util::byte_ser; 26 | 27 | use ed25519_dalek::SecretKey as edSecretKey; 28 | 29 | #[derive(Clone)] 30 | /// Arguments, mostly for encrypting decrypting a slatepack 31 | pub struct SlatepackerArgs<'a> { 32 | /// Optional sender to include in slatepack 33 | pub sender: Option, 34 | /// Optional list of recipients, for encryption 35 | pub recipients: Vec, 36 | /// Optional decryption key 37 | pub dec_key: Option<&'a edSecretKey>, 38 | } 39 | 40 | /// Helper struct to pack and unpack slatepacks 41 | #[derive(Clone)] 42 | pub struct Slatepacker<'a>(SlatepackerArgs<'a>); 43 | 44 | impl<'a> Slatepacker<'a> { 45 | /// Create with pathbuf and recipients 46 | pub fn new(args: SlatepackerArgs<'a>) -> Self { 47 | Self(args) 48 | } 49 | 50 | /// return slatepack 51 | pub fn deser_slatepack(&self, data: &[u8], decrypt: bool) -> Result { 52 | // check if data is armored, if so, remove and continue 53 | let data_len = data.len() as u64; 54 | if data_len < slatepack::min_size() || data_len > slatepack::max_size() { 55 | let msg = format!("Data invalid length"); 56 | return Err(ErrorKind::SlatepackDeser(msg).into()); 57 | } 58 | 59 | let test_header = &data[..HEADER.len()]; 60 | 61 | let data = match str::from_utf8(test_header) { 62 | Ok(s) => { 63 | if s == HEADER { 64 | SlatepackArmor::decode(data)? 65 | } else { 66 | data.to_vec() 67 | } 68 | } 69 | Err(_) => data.to_vec(), 70 | }; 71 | 72 | // try as bin first, then as json 73 | let mut slatepack = match byte_ser::from_bytes::(&data) { 74 | Ok(s) => s.0, 75 | Err(e) => { 76 | debug!("Not a valid binary slatepack: {} - Will try JSON", e); 77 | let content = String::from_utf8(data).map_err(|e| { 78 | let msg = format!("{}", e); 79 | ErrorKind::SlatepackDeser(msg) 80 | })?; 81 | serde_json::from_str(&content).map_err(|e| { 82 | let msg = format!("Error reading JSON slatepack: {}", e); 83 | ErrorKind::SlatepackDeser(msg) 84 | })? 85 | } 86 | }; 87 | 88 | slatepack.ver_check_warn(); 89 | if decrypt { 90 | slatepack.try_decrypt_payload(self.0.dec_key)?; 91 | } 92 | Ok(slatepack) 93 | } 94 | 95 | /// Create slatepack from slate and args 96 | pub fn create_slatepack(&self, slate: &Slate) -> Result { 97 | let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; 98 | let bin_slate = 99 | VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlatepackSer)?; 100 | let mut slatepack = Slatepack::default(); 101 | slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; 102 | slatepack.sender = self.0.sender.clone(); 103 | slatepack.try_encrypt_payload(self.0.recipients.clone())?; 104 | Ok(slatepack) 105 | } 106 | 107 | /// Armor a slatepack 108 | pub fn armor_slatepack(&self, slatepack: &Slatepack) -> Result { 109 | SlatepackArmor::encode(&slatepack) 110 | } 111 | 112 | /// Return/upgrade slate from slatepack 113 | pub fn get_slate(&self, slatepack: &Slatepack) -> Result { 114 | let slate_bin = 115 | byte_ser::from_bytes::(&slatepack.payload).map_err(|e| { 116 | error!("Error reading slate from armored slatepack: {}", e); 117 | let msg = format!("{}", e); 118 | ErrorKind::SlatepackDeser(msg) 119 | })?; 120 | Ok(Slate::upgrade(slate_bin.into())?) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /doc/transaction/basic-transaction-wf.puml: -------------------------------------------------------------------------------- 1 | @startuml grin-transaction 2 | 3 | title 4 | **Current Grin Tranaction Workflow** 5 | Accurate as of Oct 10, 2018 - Master branch only 6 | end title 7 | 8 | actor "Sender" as sender 9 | actor "Recipient" as recipient 10 | entity "Grin Node" as grin_node 11 | 12 | == Round 1 == 13 | 14 | note left of sender 15 | 1: Create Transaction **UUID** (for reference and maintaining correct state) 16 | 2: Set **lock_height** for transaction kernel (current chain height) 17 | 3: Select **inputs** using desired selection strategy 18 | 4: Calculate sum **inputs** blinding factors **xI** 19 | 5: Create **change_output** 20 | 6: Select blinding factor **xC** for **change_output** 21 | 7: Create lock function **sF** that locks **inputs** and stores **change_output** in wallet 22 | and identifying wallet transaction log entry **TS** linking **inputs + outputs** 23 | (Not executed at this point) 24 | end note 25 | note left of sender 26 | 8: Calculate **tx_weight**: MAX(-1 * **num_inputs** + 4 * (**num_change_outputs** + 1), 1) 27 | (+1 covers a single output on the receiver's side) 28 | 9: Calculate **fee**: **tx_weight** * 1_000_000 nG 29 | 10: Calculate total blinding excess sum for all inputs and outputs **xS1** = **xC** - **xI** (private scalar) 30 | 11: Select a random nonce **kS** (private scalar) 31 | 12: Subtract random kernel offset **oS** from **xS1**. Calculate **xS** = **xS1** - **oS** 32 | 13: Multiply **xS** and **kS** by generator G to create public curve points **xSG** and **kSG** 33 | 14: Add values to **Slate** for passing to other participants: **UUID, inputs, change_outputs,** 34 | **fee, amount, lock_height, kSG, xSG, oS** 35 | end note 36 | sender -> recipient: **Slate** 37 | == Round 2 == 38 | note right of recipient 39 | 1: Check fee against number of **inputs**, **change_outputs** +1 * **receiver_output**) 40 | 2: Create **receiver_output** 41 | 3: Choose random blinding factor for **receiver_output** **xR** (private scalar) 42 | end note 43 | note right of recipient 44 | 4: Calculate message **M** = **fee | lock_height ** 45 | 5: Choose random nonce **kR** (private scalar) 46 | 6: Multiply **xR** and **kR** by generator G to create public curve points **xRG** and **kRG** 47 | 7: Compute Schnorr challenge **e** = SHA256(**kRG** + **kSG** | **xRG** + **xSG** | **M**) 48 | 8: Compute Recipient Schnorr signature **sR** = **kR** + **e** * **xR** 49 | 9: Add **sR, xRG, kRG** to **Slate** 50 | 10: Create wallet output function **rF** that stores **receiver_output** in wallet with status "Unconfirmed" 51 | and identifying transaction log entry **TR** linking **receiver_output** with transaction. 52 | end note 53 | alt All Okay 54 | recipient --> sender: Okay - **Slate** 55 | recipient -> recipient: execute wallet output function **rF** 56 | else Any Failure 57 | recipient ->x]: Abort 58 | recipient --> sender: Error 59 | [x<- sender: Abort 60 | end 61 | == Finalize Transaction == 62 | note left of sender 63 | 1: Calculate message **M** = **fee | lock_height ** 64 | 2: Compute Schnorr challenge **e** = SHA256(**kRG** + **kSG** | **xRG** + **xSG** | **M**) 65 | 3: Verify **sR** by verifying **kRG** + **e** * **xRG** = **sRG** 66 | 4: Compute Sender Schnorr signature **sS** = **kS** + **e** * **xS** 67 | 5: Calculate final signature **s** = (**kSG**+**kRG**, **sS**+**sR**) 68 | 6: Calculate public key for **s**: **xG** = **xRG** + **xSG** 69 | 7: Verify **s** against excess values in final transaction using **xG** 70 | 8: Create Transaction Kernel Containing: 71 | Excess signature **s** 72 | Public excess **xG** 73 | **fee** 74 | **lock_height** 75 | end note 76 | sender -> sender: Create final transaction **tx** from **Slate** 77 | sender -> grin_node: Post **tx** to mempool 78 | grin_node --> recipient: "Ok" 79 | alt All Okay 80 | recipient --> sender: "Ok" - **UUID** 81 | sender -> sender: Execute wallet lock function **sF** 82 | ...Await confirmation... 83 | recipient -> grin_node: Confirm **receiver_output** 84 | recipient -> recipient: Change status of **receiver_output** to "Confirmed" 85 | sender -> grin_node: Confirm **change_output** 86 | sender -> sender: Change status of **inputs** to "Spent" 87 | sender -> sender: Change status of **change_output** to "Confirmed" 88 | else Any Error 89 | recipient -> recipient: Manually remove **receiver_output** from wallet using transaction log entry **TR** 90 | recipient ->x]: Abort 91 | recipient --> sender: Error 92 | sender -> sender: Unlock **inputs** and delete **change_output** identified in transaction log entry **TS** 93 | [x<- sender: Abort 94 | end 95 | 96 | 97 | @enduml -------------------------------------------------------------------------------- /api/tests/slate_versioning.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! core::libtx specific tests 15 | //use grin_wallet_api::foreign_rpc_client; 16 | use grin_wallet_api::run_doctest_foreign; 17 | //use grin_wallet_libwallet::VersionedSlate; 18 | use serde_json; 19 | use serde_json::Value; 20 | use tempfile::tempdir; 21 | //use grin_wallet_libwallet::slate_versions::v1::SlateV1; 22 | //use grin_wallet_libwallet::slate_versions::v2::SlateV2; 23 | 24 | // test all slate conversions 25 | //#[test] 26 | fn _receive_versioned_slate() { 27 | // as in doctests, except exercising versioning functionality 28 | // by accepting and responding with a V1 slate 29 | 30 | let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); 31 | let dir = dir 32 | .path() 33 | .to_str() 34 | .ok_or("Failed to convert tmpdir path to string.".to_owned()) 35 | .unwrap(); 36 | 37 | let v1_req = include_str!("slates/v1_req.slate"); 38 | let v1_resp = include_str!("slates/v1_res.slate"); 39 | 40 | // leave here for the ability to create earlier slate versions 41 | // for test input 42 | /*let v: Value = serde_json::from_str(v1_req).unwrap(); 43 | let v2_slate = v["params"][0].clone(); 44 | println!("{}", v2_slate); 45 | let v2_slate_str = v2_slate.to_string(); 46 | println!("{}", v2_slate_str); 47 | let v2: SlateV2 = serde_json::from_str(&v2_slate.to_string()).unwrap(); 48 | let v1 = SlateV1::from(v2); 49 | let v1_str = serde_json::to_string_pretty(&v1).unwrap(); 50 | panic!("{}", v1_str);*/ 51 | 52 | let request_val: Value = serde_json::from_str(v1_req).unwrap(); 53 | let expected_response: Value = serde_json::from_str(v1_resp).unwrap(); 54 | 55 | let response = run_doctest_foreign(request_val, dir, false, 5, true, false) 56 | .unwrap() 57 | .unwrap(); 58 | 59 | if response != expected_response { 60 | panic!( 61 | "(left != right) \nleft: {}\nright: {}", 62 | serde_json::to_string_pretty(&response).unwrap(), 63 | serde_json::to_string_pretty(&expected_response).unwrap() 64 | ); 65 | } 66 | } 67 | 68 | // TODO: Re-introduce on a new slate version 69 | 70 | /* 71 | /// call ForeignRpc::receive_tx on vs and return the result 72 | fn receive_tx(vs: VersionedSlate) -> VersionedSlate { 73 | let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); 74 | let dir = dir 75 | .path() 76 | .to_str() 77 | .ok_or("Failed to convert tmpdir path to string.".to_owned()) 78 | .unwrap(); 79 | let bound_method = foreign_rpc_client::receive_tx( 80 | vs, 81 | None, 82 | Some("Thanks for saving my dog from that tree, bddap.".into()), 83 | ) 84 | .unwrap(); 85 | let (call, tracker) = bound_method.call(); 86 | let json_response = run_doctest_foreign(call.as_request(), dir, 5, false, false) 87 | .unwrap() 88 | .unwrap(); 89 | let mut response = easy_jsonrpc::Response::from_json_response(json_response).unwrap(); 90 | tracker.get_return(&mut response).unwrap().unwrap() 91 | } 92 | 93 | #[test] 94 | fn version_unchanged() { 95 | let req: Value = serde_json::from_str(include_str!("slates/v1_req.slate")).unwrap(); 96 | let slate: VersionedSlate = serde_json::from_value(req["params"][0].clone()).unwrap(); 97 | let slate_req: Slate = slate.into(); 98 | 99 | assert_eq!( 100 | receive_tx(VersionedSlate::into_version( 101 | slate_req.clone(), 102 | SlateVersion::V0 103 | )) 104 | .version(), 105 | SlateVersion::V0 106 | ); 107 | 108 | assert_eq!( 109 | receive_tx(VersionedSlate::into_version( 110 | slate_req.clone(), 111 | SlateVersion::V1 112 | )) 113 | .version(), 114 | SlateVersion::V1 115 | ); 116 | 117 | assert_eq!( 118 | receive_tx(VersionedSlate::into_version( 119 | slate_req.clone(), 120 | SlateVersion::V2 121 | )) 122 | .version(), 123 | SlateVersion::V2 124 | ); 125 | 126 | // compile time test will remind us to update these tests when updating slate format 127 | fn _all_versions_tested(vs: VersionedSlate) { 128 | match vs { 129 | VersionedSlate::V0(_) => (), 130 | VersionedSlate::V1(_) => (), 131 | VersionedSlate::V2(_) => (), 132 | } 133 | } 134 | }*/ 135 | -------------------------------------------------------------------------------- /libwallet/src/api_impl/owner_updater.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! A threaded persistent Updater that can be controlled by a grin wallet 16 | use std::sync::atomic::{AtomicBool, Ordering}; 17 | use std::sync::mpsc::{Receiver, Sender}; 18 | use std::sync::Arc; 19 | use std::thread; 20 | use std::time::Duration; 21 | 22 | use crate::grin_keychain::Keychain; 23 | use crate::grin_util::secp::key::SecretKey; 24 | use crate::grin_util::Mutex; 25 | 26 | use crate::api_impl::owner; 27 | use crate::types::NodeClient; 28 | use crate::Error; 29 | use crate::{WalletInst, WalletLCProvider}; 30 | 31 | const MESSAGE_QUEUE_MAX_LEN: usize = 10_000; 32 | 33 | /// Update status messages which can be returned to listening clients 34 | #[derive(Clone, Debug, Serialize, Deserialize)] 35 | pub enum StatusMessage { 36 | /// Wallet is performing a regular update, matching the UTXO set against 37 | /// current wallet outputs 38 | UpdatingOutputs(String), 39 | /// Wallet is updating transactions, potentially retrieving transactions 40 | /// by kernel if needed 41 | UpdatingTransactions(String), 42 | /// Warning that the wallet is about to perform a full UTXO scan 43 | FullScanWarn(String), 44 | /// Status and percentage complete messages returned during the 45 | /// scanning process 46 | Scanning(String, u8), 47 | /// UTXO scanning is complete 48 | ScanningComplete(String), 49 | /// Warning of issues that may have occured during an update 50 | UpdateWarning(String), 51 | } 52 | 53 | /// Helper function that starts a simple log thread for updater messages 54 | pub fn start_updater_log_thread( 55 | rx: Receiver, 56 | queue: Arc>>, 57 | ) -> Result<(), Error> { 58 | let _ = thread::Builder::new() 59 | .name("wallet-updater-status".to_string()) 60 | .spawn(move || { 61 | while let Ok(m) = rx.recv() { 62 | // save to our message queue to be read by other consumers 63 | { 64 | let mut q = queue.lock(); 65 | q.insert(0, m.clone()); 66 | while q.len() > MESSAGE_QUEUE_MAX_LEN { 67 | q.pop(); 68 | } 69 | } 70 | match m { 71 | StatusMessage::UpdatingOutputs(s) => debug!("{}", s), 72 | StatusMessage::UpdatingTransactions(s) => debug!("{}", s), 73 | StatusMessage::FullScanWarn(s) => warn!("{}", s), 74 | StatusMessage::Scanning(s, _m) => { 75 | debug!("{}", s); 76 | //warn!("Scanning - {}% complete", m); 77 | } 78 | StatusMessage::ScanningComplete(s) => warn!("{}", s), 79 | StatusMessage::UpdateWarning(s) => warn!("{}", s), 80 | } 81 | } 82 | })?; 83 | 84 | Ok(()) 85 | } 86 | 87 | /// Handles and launches a background update thread 88 | pub struct Updater<'a, L, C, K> 89 | where 90 | L: WalletLCProvider<'a, C, K>, 91 | C: NodeClient + 'a, 92 | K: Keychain + 'a, 93 | { 94 | wallet_inst: Arc>>>, 95 | is_running: Arc, 96 | } 97 | 98 | impl<'a, L, C, K> Updater<'a, L, C, K> 99 | where 100 | L: WalletLCProvider<'a, C, K>, 101 | C: NodeClient + 'a, 102 | K: Keychain + 'a, 103 | { 104 | /// create a new updater 105 | pub fn new( 106 | wallet_inst: Arc>>>, 107 | is_running: Arc, 108 | ) -> Self { 109 | is_running.store(false, Ordering::Relaxed); 110 | Updater { 111 | wallet_inst, 112 | is_running, 113 | } 114 | } 115 | 116 | /// Start the updater at the given frequency 117 | pub fn run( 118 | &self, 119 | frequency: Duration, 120 | keychain_mask: Option, 121 | status_send_channel: &Option>, 122 | ) -> Result<(), Error> { 123 | self.is_running.store(true, Ordering::Relaxed); 124 | loop { 125 | let wallet_opened = { 126 | let mut w_lock = self.wallet_inst.lock(); 127 | let w_provider = w_lock.lc_provider()?; 128 | w_provider.wallet_inst().is_ok() 129 | }; 130 | // Business goes here 131 | if wallet_opened { 132 | owner::update_wallet_state( 133 | self.wallet_inst.clone(), 134 | (&keychain_mask).as_ref(), 135 | status_send_channel, 136 | false, 137 | )?; 138 | } 139 | if !self.is_running.load(Ordering::Relaxed) { 140 | break; 141 | } 142 | thread::sleep(frequency); 143 | } 144 | Ok(()) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/bin/grin-wallet.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Main for building the binary of a Grin Reference Wallet 16 | 17 | #[macro_use] 18 | extern crate clap; 19 | #[macro_use] 20 | extern crate log; 21 | use crate::config::ConfigError; 22 | use crate::core::global; 23 | use crate::util::init_logger; 24 | use clap::App; 25 | use grin_wallet_config as config; 26 | use grin_wallet_impls::HTTPNodeClient; 27 | use grin_wallet_util::grin_core as core; 28 | use grin_wallet_util::grin_util as util; 29 | use std::env; 30 | use std::path::PathBuf; 31 | 32 | use vcash_wallet::cmd; 33 | 34 | // include build information 35 | pub mod built_info { 36 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 37 | } 38 | 39 | pub fn info_strings() -> (String, String) { 40 | ( 41 | format!( 42 | "This is Grin Wallet version {}{}, built for {} by {}.", 43 | built_info::PKG_VERSION, 44 | built_info::GIT_VERSION.map_or_else(|| "".to_owned(), |v| format!(" (git {})", v)), 45 | built_info::TARGET, 46 | built_info::RUSTC_VERSION, 47 | ) 48 | .to_string(), 49 | format!( 50 | "Built with profile \"{}\", features \"{}\".", 51 | built_info::PROFILE, 52 | built_info::FEATURES_STR, 53 | ) 54 | .to_string(), 55 | ) 56 | } 57 | 58 | fn log_build_info() { 59 | let (basic_info, detailed_info) = info_strings(); 60 | info!("{}", basic_info); 61 | debug!("{}", detailed_info); 62 | } 63 | 64 | fn main() { 65 | let exit_code = real_main(); 66 | std::process::exit(exit_code); 67 | } 68 | 69 | fn real_main() -> i32 { 70 | let yml = load_yaml!("grin-wallet.yml"); 71 | let args = App::from_yaml(yml) 72 | .version(built_info::PKG_VERSION) 73 | .get_matches(); 74 | 75 | let chain_type = if args.is_present("testnet") { 76 | global::ChainTypes::Testnet 77 | } else if args.is_present("usernet") { 78 | global::ChainTypes::UserTesting 79 | } else { 80 | global::ChainTypes::Mainnet 81 | }; 82 | 83 | let mut current_dir = None; 84 | let mut create_path = false; 85 | 86 | if args.is_present("top_level_dir") { 87 | let res = args.value_of("top_level_dir"); 88 | match res { 89 | Some(d) => { 90 | current_dir = Some(PathBuf::from(d)); 91 | } 92 | None => { 93 | warn!("Argument --top_level_dir needs a value. Defaulting to current directory") 94 | } 95 | } 96 | } 97 | 98 | // special cases for certain lifecycle commands 99 | match args.subcommand() { 100 | ("init", Some(init_args)) => { 101 | if init_args.is_present("here") { 102 | current_dir = Some(env::current_dir().unwrap_or_else(|e| { 103 | panic!("Error creating config file: {}", e); 104 | })); 105 | } 106 | create_path = true; 107 | } 108 | _ => {} 109 | } 110 | 111 | // Load relevant config, try and load a wallet config file 112 | // Use defaults for configuration if config file not found anywhere 113 | let mut config = match config::initial_setup_wallet(&chain_type, current_dir, create_path) { 114 | Ok(c) => c, 115 | Err(e) => match e { 116 | ConfigError::PathNotFoundError(m) => { 117 | println!("Wallet configuration not found at {}. (Run `grin-wallet init` to create a new wallet)", m); 118 | return 0; 119 | } 120 | m => { 121 | println!("Unable to load wallet configuration: {} (Run `grin-wallet init` to create a new wallet)", m); 122 | return 0; 123 | } 124 | }, 125 | }; 126 | 127 | //config.members.as_mut().unwrap().wallet.chain_type = Some(chain_type); 128 | 129 | // Load logging config 130 | let mut l = config.members.as_mut().unwrap().logging.clone().unwrap(); 131 | // no logging to stdout if we're running cli 132 | match args.subcommand() { 133 | ("cli", _) => l.log_to_stdout = true, 134 | _ => {} 135 | }; 136 | init_logger(Some(l), None); 137 | info!( 138 | "Using wallet configuration file at {}", 139 | config.config_file_path.as_ref().unwrap().to_str().unwrap() 140 | ); 141 | 142 | log_build_info(); 143 | 144 | global::init_global_chain_type( 145 | config 146 | .members 147 | .as_ref() 148 | .unwrap() 149 | .wallet 150 | .chain_type 151 | .as_ref() 152 | .unwrap() 153 | .clone(), 154 | ); 155 | 156 | let wallet_config = config.clone().members.unwrap().wallet; 157 | let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); 158 | 159 | cmd::wallet_command(&args, config, node_client) 160 | } 161 | -------------------------------------------------------------------------------- /doc/samples/v3_api_node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-sample", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/connect": { 8 | "version": "3.4.33", 9 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", 10 | "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", 11 | "requires": { 12 | "@types/node": "*" 13 | } 14 | }, 15 | "@types/express-serve-static-core": { 16 | "version": "4.17.2", 17 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", 18 | "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", 19 | "requires": { 20 | "@types/node": "*", 21 | "@types/range-parser": "*" 22 | } 23 | }, 24 | "@types/lodash": { 25 | "version": "4.14.149", 26 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", 27 | "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" 28 | }, 29 | "@types/node": { 30 | "version": "12.12.27", 31 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.27.tgz", 32 | "integrity": "sha512-odQFl/+B9idbdS0e8IxDl2ia/LP8KZLXhV3BUeI98TrZp0uoIzQPhGd+5EtzHmT0SMOIaPd7jfz6pOHLWTtl7A==" 33 | }, 34 | "@types/range-parser": { 35 | "version": "1.2.3", 36 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", 37 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" 38 | }, 39 | "JSONStream": { 40 | "version": "1.3.5", 41 | "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", 42 | "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", 43 | "requires": { 44 | "jsonparse": "^1.2.0", 45 | "through": ">=2.2.7 <3" 46 | } 47 | }, 48 | "commander": { 49 | "version": "2.20.3", 50 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 51 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 52 | }, 53 | "es6-promise": { 54 | "version": "4.2.8", 55 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 56 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 57 | }, 58 | "es6-promisify": { 59 | "version": "5.0.0", 60 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 61 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 62 | "requires": { 63 | "es6-promise": "^4.0.3" 64 | } 65 | }, 66 | "eyes": { 67 | "version": "0.1.8", 68 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 69 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 70 | }, 71 | "jayson": { 72 | "version": "3.2.0", 73 | "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.2.0.tgz", 74 | "integrity": "sha512-DZQnwA57GcStw4soSYB2VntWXFfoWvmSarlaWePDYOWhjxT72PBM4atEBomaTaS1uqk3jFC9UO9AyWjlujo3xw==", 75 | "requires": { 76 | "@types/connect": "^3.4.32", 77 | "@types/express-serve-static-core": "^4.16.9", 78 | "@types/lodash": "^4.14.139", 79 | "@types/node": "^12.7.7", 80 | "JSONStream": "^1.3.1", 81 | "commander": "^2.12.2", 82 | "es6-promisify": "^5.0.0", 83 | "eyes": "^0.1.8", 84 | "json-stringify-safe": "^5.0.1", 85 | "lodash": "^4.17.15", 86 | "uuid": "^3.2.1" 87 | } 88 | }, 89 | "json-stringify-safe": { 90 | "version": "5.0.1", 91 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 92 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 93 | }, 94 | "jsonparse": { 95 | "version": "1.3.1", 96 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", 97 | "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" 98 | }, 99 | "lodash": { 100 | "version": "4.17.19", 101 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 102 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" 103 | }, 104 | "through": { 105 | "version": "2.3.8", 106 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 107 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 108 | }, 109 | "uuid": { 110 | "version": "3.4.0", 111 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 112 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /controller/tests/self_send.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! Test a wallet sending to self 15 | #[macro_use] 16 | extern crate log; 17 | extern crate grin_wallet_controller as wallet; 18 | extern crate grin_wallet_impls as impls; 19 | 20 | use grin_wallet_util::grin_core as core; 21 | 22 | use grin_wallet_libwallet as libwallet; 23 | use impls::test_framework::{self, LocalWalletClient}; 24 | use libwallet::InitTxArgs; 25 | use std::sync::atomic::Ordering; 26 | use std::thread; 27 | use std::time::Duration; 28 | 29 | #[macro_use] 30 | mod common; 31 | use common::{clean_output_dir, create_wallet_proxy, setup}; 32 | 33 | /// self send impl 34 | fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { 35 | // Create a new proxy to simulate server and wallet responses 36 | let mut wallet_proxy = create_wallet_proxy(test_dir); 37 | let chain = wallet_proxy.chain.clone(); 38 | let stopper = wallet_proxy.running.clone(); 39 | 40 | // Create a new wallet test client, and set its queues to communicate with the 41 | // proxy 42 | create_wallet_and_add!( 43 | client1, 44 | wallet1, 45 | mask1_i, 46 | test_dir, 47 | "wallet1", 48 | None, 49 | &mut wallet_proxy, 50 | true 51 | ); 52 | let mask1 = (&mask1_i).as_ref(); 53 | 54 | // Set the wallet proxy listener running 55 | thread::spawn(move || { 56 | if let Err(e) = wallet_proxy.run() { 57 | error!("Wallet Proxy error: {}", e); 58 | } 59 | }); 60 | 61 | // few values to keep things shorter 62 | let reward = core::consensus::REWARD_ADJUSTED; 63 | 64 | // add some accounts 65 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 66 | api.create_account_path(m, "mining")?; 67 | api.create_account_path(m, "listener")?; 68 | Ok(()) 69 | })?; 70 | 71 | // Get some mining done 72 | { 73 | wallet_inst!(wallet1, w); 74 | w.set_parent_key_id_by_name("mining")?; 75 | } 76 | let mut bh = 10u64; 77 | let _ = 78 | test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); 79 | 80 | // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) 81 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 82 | let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; 83 | assert!(wallet1_refreshed); 84 | assert_eq!(wallet1_info.last_confirmed_height, bh); 85 | assert_eq!(wallet1_info.total, 42 * reward); 86 | // send to send 87 | let args = InitTxArgs { 88 | src_acct_name: Some("mining".to_owned()), 89 | amount: reward * 2, 90 | minimum_confirmations: 2, 91 | max_outputs: 500, 92 | num_change_outputs: 1, 93 | selection_strategy_is_use_all: true, 94 | ..Default::default() 95 | }; 96 | let mut slate = api.init_send_tx(m, args)?; 97 | api.tx_lock_outputs(m, &slate)?; 98 | // Send directly to self 99 | wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { 100 | slate = api.receive_tx(&slate, Some("listener"), None)?; 101 | Ok(()) 102 | })?; 103 | slate = api.finalize_tx(m, &slate)?; 104 | api.post_tx(m, &slate, false)?; // mines a block 105 | bh += 1; 106 | Ok(()) 107 | })?; 108 | 109 | let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); 110 | bh += 3; 111 | 112 | // Check total in mining account 113 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 114 | let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; 115 | assert!(wallet1_refreshed); 116 | assert_eq!(wallet1_info.last_confirmed_height, bh); 117 | assert_eq!(wallet1_info.total, 46 * reward - reward * 2); 118 | Ok(()) 119 | })?; 120 | 121 | // Check total in 'listener' account 122 | { 123 | wallet_inst!(wallet1, w); 124 | w.set_parent_key_id_by_name("listener")?; 125 | } 126 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 127 | let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; 128 | assert!(wallet1_refreshed); 129 | assert_eq!(wallet1_info.last_confirmed_height, bh); 130 | assert_eq!(wallet1_info.total, 2 * reward); 131 | Ok(()) 132 | })?; 133 | 134 | // let logging finish 135 | stopper.store(false, Ordering::Relaxed); 136 | thread::sleep(Duration::from_millis(200)); 137 | Ok(()) 138 | } 139 | 140 | #[test] 141 | fn wallet_self_send() { 142 | let test_dir = "test_output/self_send"; 143 | setup(test_dir); 144 | if let Err(e) = self_send_test_impl(test_dir) { 145 | panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 146 | } 147 | clean_output_dir(test_dir); 148 | } 149 | -------------------------------------------------------------------------------- /impls/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Implementation specific error types 16 | use crate::core::libtx; 17 | use crate::keychain; 18 | use crate::libwallet; 19 | use crate::util::secp; 20 | use failure::{Backtrace, Context, Fail}; 21 | use grin_wallet_util::OnionV3AddressError; 22 | use std::env; 23 | use std::fmt::{self, Display}; 24 | 25 | /// Error definition 26 | #[derive(Debug)] 27 | pub struct Error { 28 | pub inner: Context, 29 | } 30 | 31 | /// Wallet errors, mostly wrappers around underlying crypto or I/O errors. 32 | #[derive(Clone, Eq, PartialEq, Debug, Fail)] 33 | pub enum ErrorKind { 34 | /// LibTX Error 35 | #[fail(display = "LibTx Error")] 36 | LibTX(libtx::ErrorKind), 37 | 38 | /// LibWallet Error 39 | #[fail(display = "LibWallet Error: {}", _1)] 40 | LibWallet(libwallet::ErrorKind, String), 41 | 42 | /// Keychain error 43 | #[fail(display = "Keychain error")] 44 | Keychain(keychain::Error), 45 | 46 | /// Onion V3 Address Error 47 | #[fail(display = "Onion V3 Address Error")] 48 | OnionV3Address(OnionV3AddressError), 49 | 50 | /// Error when formatting json 51 | #[fail(display = "IO error")] 52 | IO, 53 | 54 | /// Secp Error 55 | #[fail(display = "Secp error")] 56 | Secp(secp::Error), 57 | 58 | /// Error when formatting json 59 | #[fail(display = "Serde JSON error")] 60 | Format, 61 | 62 | /// Wallet seed already exists 63 | #[fail(display = "Wallet seed file exists: {}", _0)] 64 | WalletSeedExists(String), 65 | 66 | /// Wallet seed doesn't exist 67 | #[fail(display = "Wallet seed doesn't exist error")] 68 | WalletSeedDoesntExist, 69 | 70 | /// Wallet seed doesn't exist 71 | #[fail(display = "Wallet doesn't exist at {}. {}", _0, _1)] 72 | WalletDoesntExist(String, String), 73 | 74 | /// Enc/Decryption Error 75 | #[fail(display = "Enc/Decryption error (check password?)")] 76 | Encryption, 77 | 78 | /// BIP 39 word list 79 | #[fail(display = "BIP39 Mnemonic (word list) Error")] 80 | Mnemonic, 81 | 82 | /// Command line argument error 83 | #[fail(display = "{}", _0)] 84 | ArgumentError(String), 85 | 86 | /// Generating ED25519 Public Key 87 | #[fail(display = "Error generating ed25519 secret key: {}", _0)] 88 | ED25519Key(String), 89 | 90 | /// Checking for onion address 91 | #[fail(display = "Address is not an Onion v3 Address: {}", _0)] 92 | NotOnion(String), 93 | 94 | /// Other 95 | #[fail(display = "Generic error: {}", _0)] 96 | GenericError(String), 97 | } 98 | 99 | impl Fail for Error { 100 | fn cause(&self) -> Option<&dyn Fail> { 101 | self.inner.cause() 102 | } 103 | 104 | fn backtrace(&self) -> Option<&Backtrace> { 105 | self.inner.backtrace() 106 | } 107 | } 108 | 109 | impl Display for Error { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | let show_bt = match env::var("RUST_BACKTRACE") { 112 | Ok(r) => r == "1", 113 | Err(_) => false, 114 | }; 115 | let backtrace = match self.backtrace() { 116 | Some(b) => format!("{}", b), 117 | None => String::from("Unknown"), 118 | }; 119 | let inner_output = format!("{}", self.inner,); 120 | let backtrace_output = format!("\nBacktrace: {}", backtrace); 121 | let mut output = inner_output; 122 | if show_bt { 123 | output.push_str(&backtrace_output); 124 | } 125 | Display::fmt(&output, f) 126 | } 127 | } 128 | 129 | impl Error { 130 | /// get kind 131 | pub fn kind(&self) -> ErrorKind { 132 | self.inner.get_context().clone() 133 | } 134 | /// get cause 135 | pub fn cause(&self) -> Option<&dyn Fail> { 136 | self.inner.cause() 137 | } 138 | /// get backtrace 139 | pub fn backtrace(&self) -> Option<&Backtrace> { 140 | self.inner.backtrace() 141 | } 142 | } 143 | 144 | impl From for Error { 145 | fn from(kind: ErrorKind) -> Error { 146 | Error { 147 | inner: Context::new(kind), 148 | } 149 | } 150 | } 151 | 152 | impl From> for Error { 153 | fn from(inner: Context) -> Error { 154 | Error { inner: inner } 155 | } 156 | } 157 | 158 | impl From for Error { 159 | fn from(error: keychain::Error) -> Error { 160 | Error { 161 | inner: Context::new(ErrorKind::Keychain(error)), 162 | } 163 | } 164 | } 165 | 166 | impl From for Error { 167 | fn from(error: secp::Error) -> Error { 168 | Error { 169 | inner: Context::new(ErrorKind::Secp(error)), 170 | } 171 | } 172 | } 173 | 174 | impl From for Error { 175 | fn from(error: libwallet::Error) -> Error { 176 | Error { 177 | inner: Context::new(ErrorKind::LibWallet(error.kind(), format!("{}", error))), 178 | } 179 | } 180 | } 181 | 182 | impl From for Error { 183 | fn from(error: libtx::Error) -> Error { 184 | Error { 185 | inner: Context::new(ErrorKind::LibTX(error.kind())), 186 | } 187 | } 188 | } 189 | 190 | impl From for Error { 191 | fn from(error: OnionV3AddressError) -> Error { 192 | Error::from(ErrorKind::OnionV3Address(error)) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /libwallet/src/api_impl/foreign.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Generic implementation of owner API functions 16 | use strum::IntoEnumIterator; 17 | 18 | use crate::api_impl::owner::finalize_tx as owner_finalize; 19 | use crate::api_impl::owner::{check_ttl, post_tx}; 20 | use crate::grin_keychain::Keychain; 21 | use crate::grin_util::secp::key::SecretKey; 22 | use crate::internal::{selection, tx, updater}; 23 | use crate::slate_versions::SlateVersion; 24 | use crate::TokenTxLogEntryType; 25 | use crate::{ 26 | address, BlockFees, CbData, Error, ErrorKind, NodeClient, Slate, SlateState, TxLogEntryType, 27 | VersionInfo, WalletBackend, 28 | }; 29 | 30 | const FOREIGN_API_VERSION: u16 = 2; 31 | 32 | /// Return the version info 33 | pub fn check_version() -> VersionInfo { 34 | VersionInfo { 35 | foreign_api_version: FOREIGN_API_VERSION, 36 | supported_slate_versions: SlateVersion::iter().collect(), 37 | } 38 | } 39 | 40 | /// Build a coinbase transaction 41 | pub fn build_coinbase<'a, T: ?Sized, C, K>( 42 | w: &mut T, 43 | keychain_mask: Option<&SecretKey>, 44 | block_fees: &BlockFees, 45 | test_mode: bool, 46 | ) -> Result 47 | where 48 | T: WalletBackend<'a, C, K>, 49 | C: NodeClient + 'a, 50 | K: Keychain + 'a, 51 | { 52 | updater::build_coinbase(&mut *w, keychain_mask, block_fees, test_mode) 53 | } 54 | 55 | /// Receive a tx as recipient 56 | pub fn receive_tx<'a, T: ?Sized, C, K>( 57 | w: &mut T, 58 | keychain_mask: Option<&SecretKey>, 59 | slate: &Slate, 60 | dest_acct_name: Option<&str>, 61 | use_test_rng: bool, 62 | ) -> Result 63 | where 64 | T: WalletBackend<'a, C, K>, 65 | C: NodeClient + 'a, 66 | K: Keychain + 'a, 67 | { 68 | let mut ret_slate = slate.clone(); 69 | check_ttl(w, &ret_slate)?; 70 | let parent_key_id = match dest_acct_name { 71 | Some(d) => { 72 | let pm = w.get_acct_path(d.to_owned())?; 73 | match pm { 74 | Some(p) => p.path, 75 | None => w.parent_key_id(), 76 | } 77 | } 78 | None => w.parent_key_id(), 79 | }; 80 | // Don't do this multiple times 81 | if slate.token_type.clone().is_some() { 82 | let tx = updater::retrieve_token_txs( 83 | &mut *w, 84 | None, 85 | Some(ret_slate.id), 86 | Some(&parent_key_id), 87 | use_test_rng, 88 | )?; 89 | for t in &tx { 90 | if t.tx_type == TokenTxLogEntryType::TokenTxReceived { 91 | return Err(ErrorKind::TransactionAlreadyReceived(ret_slate.id.to_string()).into()); 92 | } 93 | } 94 | } else { 95 | let tx = updater::retrieve_txs( 96 | &mut *w, 97 | None, 98 | Some(ret_slate.id), 99 | Some(&parent_key_id), 100 | use_test_rng, 101 | )?; 102 | for t in &tx { 103 | if t.tx_type == TxLogEntryType::TxReceived { 104 | return Err(ErrorKind::TransactionAlreadyReceived(ret_slate.id.to_string()).into()); 105 | } 106 | } 107 | } 108 | 109 | ret_slate.tx = Some(Slate::empty_transaction()); 110 | 111 | let height = w.last_confirmed_height()?; 112 | let keychain = w.keychain(keychain_mask)?; 113 | 114 | let context = tx::add_output_to_slate( 115 | &mut *w, 116 | keychain_mask, 117 | &mut ret_slate, 118 | height, 119 | &parent_key_id, 120 | false, 121 | use_test_rng, 122 | )?; 123 | 124 | let excess = ret_slate.calc_excess(keychain.secp())?; 125 | 126 | if let Some(ref mut p) = ret_slate.payment_proof { 127 | let sig = tx::create_payment_proof_signature( 128 | ret_slate.token_type.clone(), 129 | ret_slate.amount, 130 | &excess, 131 | p.sender_address, 132 | address::address_from_derivation_path(&keychain, &parent_key_id, 0)?, 133 | )?; 134 | 135 | p.receiver_signature = Some(sig); 136 | } 137 | 138 | ret_slate.amount = 0; 139 | ret_slate.fee = 0; 140 | ret_slate.remove_other_sigdata( 141 | &keychain, 142 | &context.sec_nonce, 143 | &context.sec_key, 144 | &context.token_sec_key, 145 | )?; 146 | ret_slate.state = SlateState::Standard2; 147 | 148 | Ok(ret_slate) 149 | } 150 | 151 | /// Receive a tx that this wallet has issued 152 | pub fn finalize_tx<'a, T: ?Sized, C, K>( 153 | w: &mut T, 154 | keychain_mask: Option<&SecretKey>, 155 | slate: &Slate, 156 | post_automatically: bool, 157 | ) -> Result 158 | where 159 | T: WalletBackend<'a, C, K>, 160 | C: NodeClient + 'a, 161 | K: Keychain + 'a, 162 | { 163 | let mut sl = slate.clone(); 164 | let context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; 165 | if sl.state == SlateState::Invoice2 { 166 | check_ttl(w, &sl)?; 167 | 168 | let mut temp_ctx = context.clone(); 169 | temp_ctx.sec_key = context.initial_sec_key.clone(); 170 | temp_ctx.sec_nonce = context.initial_sec_nonce.clone(); 171 | selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &temp_ctx, false)?; 172 | 173 | tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; 174 | tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, true)?; 175 | { 176 | let mut batch = w.batch(keychain_mask)?; 177 | batch.delete_private_context(sl.id.as_bytes())?; 178 | batch.commit()?; 179 | } 180 | sl.state = SlateState::Invoice3; 181 | sl.amount = 0; 182 | } else { 183 | sl = owner_finalize(w, keychain_mask, slate)?; 184 | } 185 | if post_automatically { 186 | post_tx(w.w2n_client(), sl.tx_or_err()?, true)?; 187 | } 188 | Ok(sl) 189 | } 190 | -------------------------------------------------------------------------------- /controller/tests/payment_proofs.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! tests differing accounts in the same wallet 15 | #[macro_use] 16 | extern crate log; 17 | extern crate grin_wallet_controller as wallet; 18 | extern crate grin_wallet_impls as impls; 19 | extern crate grin_wallet_util; 20 | 21 | use grin_wallet_libwallet as libwallet; 22 | use impls::test_framework::{self, LocalWalletClient}; 23 | use libwallet::{InitTxArgs, Slate}; 24 | use std::sync::atomic::Ordering; 25 | use std::thread; 26 | use std::time::Duration; 27 | 28 | #[macro_use] 29 | mod common; 30 | use common::{clean_output_dir, create_wallet_proxy, setup}; 31 | 32 | /// Various tests on accounts within the same wallet 33 | fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { 34 | // Create a new proxy to simulate server and wallet responses 35 | let mut wallet_proxy = create_wallet_proxy(test_dir); 36 | let chain = wallet_proxy.chain.clone(); 37 | let stopper = wallet_proxy.running.clone(); 38 | 39 | create_wallet_and_add!( 40 | client1, 41 | wallet1, 42 | mask1_i, 43 | test_dir, 44 | "wallet1", 45 | None, 46 | &mut wallet_proxy, 47 | false 48 | ); 49 | 50 | let mask1 = (&mask1_i).as_ref(); 51 | 52 | create_wallet_and_add!( 53 | client2, 54 | wallet2, 55 | mask2_i, 56 | test_dir, 57 | "wallet2", 58 | None, 59 | &mut wallet_proxy, 60 | false 61 | ); 62 | 63 | let mask2 = (&mask2_i).as_ref(); 64 | 65 | // Set the wallet proxy listener running 66 | thread::spawn(move || { 67 | if let Err(e) = wallet_proxy.run() { 68 | error!("Wallet Proxy error: {}", e); 69 | } 70 | }); 71 | 72 | // few values to keep things shorter 73 | 74 | // Do some mining 75 | let bh = 10u64; 76 | let _ = 77 | test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); 78 | 79 | let mut address = None; 80 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { 81 | address = Some(api.get_slatepack_address(m, 0)?); 82 | Ok(()) 83 | })?; 84 | 85 | println!("Public address is: {:?}", address); 86 | let amount = 60_000_000_000; 87 | let mut slate = Slate::blank(1, false); 88 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { 89 | // note this will increment the block count as part of the transaction "Posting" 90 | let args = InitTxArgs { 91 | src_acct_name: None, 92 | amount: amount, 93 | minimum_confirmations: 2, 94 | max_outputs: 500, 95 | num_change_outputs: 1, 96 | selection_strategy_is_use_all: true, 97 | payment_proof_recipient_address: address.clone(), 98 | ..Default::default() 99 | }; 100 | let slate_i = sender_api.init_send_tx(m, args)?; 101 | 102 | assert_eq!( 103 | slate_i.payment_proof.as_ref().unwrap().receiver_address, 104 | address.as_ref().unwrap().pub_key, 105 | ); 106 | println!( 107 | "Sender addr: {:?}", 108 | slate_i.payment_proof.as_ref().unwrap().sender_address 109 | ); 110 | 111 | // Check we are creating a tx with kernel features 0 112 | // We will check this produces a Plain kernel later. 113 | assert_eq!(0, slate.kernel_features); 114 | 115 | slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; 116 | sender_api.tx_lock_outputs(m, &slate)?; 117 | 118 | // Ensure what's stored in TX log for payment proof is correct 119 | let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 120 | assert!(txs[0].payment_proof.is_some()); 121 | let pp = txs[0].clone().payment_proof.unwrap(); 122 | assert_eq!( 123 | pp.receiver_address, 124 | slate_i.payment_proof.as_ref().unwrap().receiver_address 125 | ); 126 | assert!(pp.receiver_signature.is_some()); 127 | assert_eq!(pp.sender_address_path, 0); 128 | assert_eq!(pp.sender_signature, None); 129 | 130 | // check we should get an error at this point since proof is not complete 131 | let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id)); 132 | assert!(pp.is_err()); 133 | 134 | slate = sender_api.finalize_tx(m, &slate)?; 135 | sender_api.post_tx(m, &slate, true)?; 136 | Ok(()) 137 | })?; 138 | 139 | let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); 140 | 141 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { 142 | // Check payment proof here 143 | let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?; 144 | 145 | println!("Payment proof: {:?}", pp); 146 | 147 | // verify, should be good 148 | let res = sender_api.verify_payment_proof(m, &pp)?; 149 | assert_eq!(res, (true, false)); 150 | 151 | // Modify values, should not be good 152 | pp.amount = 20; 153 | let res = sender_api.verify_payment_proof(m, &pp); 154 | assert!(res.is_err()); 155 | Ok(()) 156 | })?; 157 | 158 | // let logging finish 159 | stopper.store(false, Ordering::Relaxed); 160 | thread::sleep(Duration::from_millis(200)); 161 | Ok(()) 162 | } 163 | 164 | #[test] 165 | fn payment_proofs() { 166 | let test_dir = "test_output/payment_proofs"; 167 | setup(test_dir); 168 | if let Err(e) = payment_proofs_test_impl(test_dir) { 169 | panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 170 | } 171 | clean_output_dir(test_dir); 172 | } 173 | -------------------------------------------------------------------------------- /controller/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! common functions for tests (instantiating wallet and proxy, mostly) 15 | extern crate grin_wallet_controller as wallet; 16 | extern crate grin_wallet_impls as impls; 17 | extern crate grin_wallet_libwallet as libwallet; 18 | 19 | use grin_wallet_util::grin_core as core; 20 | use grin_wallet_util::grin_keychain as keychain; 21 | use grin_wallet_util::grin_util as util; 22 | 23 | use self::core::global; 24 | use self::core::global::ChainTypes; 25 | use self::keychain::ExtKeychain; 26 | use self::libwallet::WalletInst; 27 | use impls::test_framework::{LocalWalletClient, WalletProxy}; 28 | use impls::{DefaultLCProvider, DefaultWalletImpl}; 29 | use std::fs; 30 | use std::sync::Arc; 31 | use util::secp::key::SecretKey; 32 | use util::{Mutex, ZeroingString}; 33 | 34 | #[macro_export] 35 | macro_rules! wallet_inst { 36 | ($wallet:ident, $w: ident) => { 37 | let mut w_lock = $wallet.lock(); 38 | let lc = w_lock.lc_provider()?; 39 | let $w = lc.wallet_inst()?; 40 | }; 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! create_wallet_and_add { 45 | ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => { 46 | let $client = LocalWalletClient::new($name, $proxy.tx.clone()); 47 | let ($wallet, $mask) = common::create_local_wallet( 48 | $test_dir, 49 | $name, 50 | $seed_phrase.clone(), 51 | $client.clone(), 52 | $create_mask, 53 | ); 54 | $proxy.add_wallet( 55 | $name, 56 | $client.get_send_instance(), 57 | $wallet.clone(), 58 | $mask.clone(), 59 | ); 60 | }; 61 | } 62 | 63 | #[macro_export] 64 | macro_rules! open_wallet_and_add { 65 | ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $proxy: expr, $create_mask: expr) => { 66 | let $client = LocalWalletClient::new($name, $proxy.tx.clone()); 67 | let ($wallet, $mask) = 68 | common::open_local_wallet($test_dir, $name, $client.clone(), $create_mask); 69 | $proxy.add_wallet( 70 | $name, 71 | $client.get_send_instance(), 72 | $wallet.clone(), 73 | $mask.clone(), 74 | ); 75 | }; 76 | } 77 | pub fn clean_output_dir(test_dir: &str) { 78 | let path = std::path::Path::new(test_dir); 79 | if path.is_dir() { 80 | fs::remove_dir_all(test_dir).unwrap(); 81 | } 82 | } 83 | 84 | pub fn setup(test_dir: &str) { 85 | util::init_test_logger(); 86 | clean_output_dir(test_dir); 87 | global::set_local_chain_type(ChainTypes::AutomatedTesting); 88 | } 89 | 90 | /// Some tests require the global chain_type to be configured due to threads being spawned internally. 91 | /// It is recommended to avoid relying on this if at all possible as global chain_type 92 | /// leaks across multiple tests and will likely have unintended consequences. 93 | #[allow(dead_code)] 94 | pub fn setup_global_chain_type() { 95 | global::init_global_chain_type(global::ChainTypes::AutomatedTesting); 96 | } 97 | 98 | pub fn create_wallet_proxy( 99 | test_dir: &str, 100 | ) -> WalletProxy, LocalWalletClient, ExtKeychain> 101 | { 102 | WalletProxy::new(test_dir) 103 | } 104 | 105 | pub fn create_local_wallet( 106 | test_dir: &str, 107 | name: &str, 108 | mnemonic: Option, 109 | client: LocalWalletClient, 110 | create_mask: bool, 111 | ) -> ( 112 | Arc< 113 | Mutex< 114 | Box< 115 | dyn WalletInst< 116 | 'static, 117 | DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, 118 | LocalWalletClient, 119 | ExtKeychain, 120 | >, 121 | >, 122 | >, 123 | >, 124 | Option, 125 | ) { 126 | let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) 127 | as Box< 128 | dyn WalletInst< 129 | DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, 130 | LocalWalletClient, 131 | ExtKeychain, 132 | >, 133 | >; 134 | let lc = wallet.lc_provider().unwrap(); 135 | let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name)); 136 | lc.create_wallet(None, mnemonic, 32, ZeroingString::from(""), false) 137 | .unwrap(); 138 | let mask = lc 139 | .open_wallet(None, ZeroingString::from(""), create_mask, false) 140 | .unwrap(); 141 | (Arc::new(Mutex::new(wallet)), mask) 142 | } 143 | 144 | #[allow(dead_code)] 145 | pub fn open_local_wallet( 146 | test_dir: &str, 147 | name: &str, 148 | client: LocalWalletClient, 149 | create_mask: bool, 150 | ) -> ( 151 | Arc< 152 | Mutex< 153 | Box< 154 | dyn WalletInst< 155 | 'static, 156 | DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, 157 | LocalWalletClient, 158 | ExtKeychain, 159 | >, 160 | >, 161 | >, 162 | >, 163 | Option, 164 | ) { 165 | let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) 166 | as Box< 167 | dyn WalletInst< 168 | DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, 169 | LocalWalletClient, 170 | ExtKeychain, 171 | >, 172 | >; 173 | let lc = wallet.lc_provider().unwrap(); 174 | let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name)); 175 | let mask = lc 176 | .open_wallet(None, ZeroingString::from(""), create_mask, false) 177 | .unwrap(); 178 | (Arc::new(Mutex::new(wallet)), mask) 179 | } 180 | -------------------------------------------------------------------------------- /doc/design/design.md: -------------------------------------------------------------------------------- 1 | # Grin Wallet + Library Design 2 | 3 | ![wallet design](wallet-arch.png) 4 | 5 | ## High Level Wallet Design Overview 6 | 7 | The current Grin `wallet` crate provides several layers of libraries, services, and traits that can be mixed, matched and reimplemented to support 8 | various needs within the default Grin wallet as well as provide a set of useful library functions for 3rd-party implementors. At a very high level, 9 | the code is organized into the following components (from highest-level to lowest): 10 | 11 | * **Command Line Client** - The command line client invoked by `grin-wallet [command]`, simply instantiates the other components below 12 | and parses command line arguments as needed. 13 | * **Web Wallet Client** - [Work In Progress] A web wallet client accessible from the local machine only. Current code can be viewed here: 14 | https://github.com/mimblewimble/grin-web-wallet 15 | * **Static File Server** - [TBD] A means of serving up the web wallet client above to the user (still under consideration) 16 | * **libWallet** - A high level wallet library that provides functions for the default grin wallet. The functions in here can be somewhat 17 | specific to how the grin wallet does things, but could still be reused by 3rd party implementors following the same basic principles as grin 18 | does. Major functionality is split into: 19 | * **Owner API** - An API that provides information that should only be viewable by the wallet owner 20 | * **Foreign API** - An API to communicate with other wallets and external grin nodes 21 | * **Service Controller** - A Controller that instantiates the above APIs (either locally or via web services) 22 | * **Internal Functions** Helper functions to perform needed wallet tasks, such as selecting coins, updating wallet outputs with 23 | results from a Grin node, etc. 24 | * **libTx** - Library that provides lower-level transaction building, rangeproof and signing functions, highly-reusable by wallet implementors. 25 | * **Wallet Traits** - A set of generic traits defined within libWallet and the `keychain` crate . A wallet implementation such as Grin's current 26 | default only needs to implement these traits in order to provide a wallet: 27 | * **NodeClient** - Defines communication between the wallet, a running grin node and/or other wallets 28 | * **WalletBackend** - Defines the storage implementation of the wallet 29 | * **KeyChain** - Defines key derivation operations 30 | 31 | ## Module-Specific Notes 32 | 33 | A full API-Description for each of these parts is still TBD (and should be generated by rustdoc rather than repeated here). However a few design 34 | notes on each module are worth mentioning here. 35 | 36 | ### Web Wallet Client / Static File Server 37 | 38 | This component is not a 3rd-party hosted 'Web Wallet' , but a client meant to be run on the local machine only by the wallet owner. It should provide 39 | a usable browser interface into the wallet, that should be functionally equivalent to using the command line but (hopefully) far easier to use. 40 | It is currently not being included by a default grin build, although the required listener is currently being run by default. To build and test this 41 | component, see instructions on the [project page](https://github.com/mimblewimble/grin-web-wallet). The 'Static File Server' is still under 42 | discussion, and concerns how to provide the web-wallet to the user in a default Grin build. 43 | 44 | ### Owner API / Foreign API 45 | 46 | The high-level wallet API has been split into two, to allow for different requirements on each. For instance, the Foreign API would listen on 47 | an external-facing port, and therefore potentially has different security requirements from the Owner API, which can simply be bound to localhost 48 | only. 49 | 50 | ### libTX 51 | 52 | Transactions are built using the concept of a 'Slate', which is a data structure that gets passed around to all participants in a transaction, 53 | with each appending their Inputs, Outputs or Signatures to it to build a completed wallet transaction. Although the current mode of operation in 54 | the default client only supports single-user - single recipient, an arbitrary number of participants to a transaction is supported within libTX. 55 | 56 | ### Wallet Traits 57 | 58 | In the current code, a Wallet implementation is just a combination of these three traits. The vast majority of functions within libwallet 59 | and libTX have a signature similar to the following: 60 | 61 | ```rust 62 | pub fn retrieve_outputs( 63 | !·wallet: &mut T, 64 | !·show_spent: bool, 65 | !·tx_id: Option, 66 | ) -> Result, Error> 67 | where 68 | !·T: WalletBackend, 69 | !·C: NodeClient, 70 | !·K: Keychain, 71 | { 72 | ``` 73 | 74 | With `T` in this instance being a class that implements the `WalletBackend` trait, which is further parameterized with implementations of 75 | `NodeClient` and `Keychain`. 76 | 77 | There is currently only a single implementation of the Keychain trait within the Grin code, in the `keychain` crate exported as `ExtKeyChain`. 78 | The `Keychain` trait makes several assumptions about the underlying implementation, particularly that it will adhere to a 79 | [BIP-38 style](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki) 'master key -> child key' model. 80 | 81 | There are two implementations of `NodeClient` within the code, the main version being the `HTTPNodeClient` found within `wallet/src/client.rs` and 82 | the seconds a test client that communicates with an in-process instance of a chain. The NodeClient isolates all network calls, so upgrading wallet 83 | communication from the current simple http interaction to a more secure protocol (or allowing for many options) should be a simple 84 | matter of dropping in different `NodeClient` implementations. 85 | 86 | There are also two implementations of `WalletBackend` within the code at the base of the `wallet` crate. `LMDBBackend` found within 87 | `wallet/src/lmdb_wallet.rs` is the main implementation, and is now used by all grin wallet commands. The earlier `FileWallet` still exists 88 | within the code, however it is not invoked, and given there are no real advantages to running it over a DB implementation, development on it 89 | has been dropped in favour of the LMDB implementation. -------------------------------------------------------------------------------- /impls/src/adapters/slatepack.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Slatepack Output 'plugin' implementation 16 | use std::fs::{metadata, File}; 17 | use std::io::{Read, Write}; 18 | use std::path::PathBuf; 19 | 20 | use crate::libwallet::{slatepack, Error, ErrorKind, Slate, Slatepack, SlatepackBin, Slatepacker}; 21 | use crate::{SlateGetter, SlatePutter}; 22 | use grin_wallet_util::byte_ser; 23 | 24 | // And Slate putter impls to output to files 25 | pub struct PathToSlatepack<'a> { 26 | pub pathbuf: PathBuf, 27 | pub packer: &'a Slatepacker<'a>, 28 | pub armor_output: bool, 29 | } 30 | 31 | impl<'a> PathToSlatepack<'a> { 32 | /// Create with pathbuf and recipients 33 | pub fn new(pathbuf: PathBuf, packer: &'a Slatepacker<'a>, armor_output: bool) -> Self { 34 | Self { 35 | pathbuf, 36 | packer, 37 | armor_output, 38 | } 39 | } 40 | 41 | pub fn get_slatepack_file_contents(&self) -> Result, Error> { 42 | let metadata = metadata(&self.pathbuf)?; 43 | let len = metadata.len(); 44 | let min_len = slatepack::min_size(); 45 | let max_len = slatepack::max_size(); 46 | if len < min_len || len > max_len { 47 | let msg = format!( 48 | "Data is invalid length: {} | min: {}, max: {} |", 49 | len, min_len, max_len 50 | ); 51 | return Err(ErrorKind::SlatepackDeser(msg).into()); 52 | } 53 | let mut pub_tx_f = File::open(&self.pathbuf)?; 54 | let mut data = Vec::new(); 55 | pub_tx_f.read_to_end(&mut data)?; 56 | Ok(data) 57 | } 58 | 59 | pub fn get_slatepack(&self, decrypt: bool) -> Result { 60 | let data = self.get_slatepack_file_contents()?; 61 | self.packer.deser_slatepack(&data, decrypt) 62 | } 63 | } 64 | 65 | impl<'a> SlatePutter for PathToSlatepack<'a> { 66 | fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> { 67 | let slatepack = self.packer.create_slatepack(slate)?; 68 | let mut pub_tx = File::create(&self.pathbuf)?; 69 | if as_bin { 70 | if self.armor_output { 71 | let armored = self.packer.armor_slatepack(&slatepack)?; 72 | pub_tx.write_all(armored.as_bytes())?; 73 | } else { 74 | pub_tx.write_all( 75 | &byte_ser::to_bytes(&SlatepackBin(slatepack)) 76 | .map_err(|_| ErrorKind::SlatepackSer)?, 77 | )?; 78 | } 79 | } else { 80 | pub_tx.write_all( 81 | serde_json::to_string_pretty(&slatepack) 82 | .map_err(|_| ErrorKind::SlateSer)? 83 | .as_bytes(), 84 | )?; 85 | } 86 | pub_tx.sync_all()?; 87 | Ok(()) 88 | } 89 | } 90 | 91 | impl<'a> SlateGetter for PathToSlatepack<'a> { 92 | fn get_tx(&self) -> Result<(Slate, bool), Error> { 93 | let data = self.get_slatepack_file_contents()?; 94 | let slatepack = self.packer.deser_slatepack(&data, true)?; 95 | Ok((self.packer.get_slate(&slatepack)?, true)) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use std::fs; 103 | 104 | use grin_wallet_util::grin_core::global; 105 | 106 | fn clean_output_dir(test_dir: &str) { 107 | let _ = fs::remove_dir_all(test_dir); 108 | } 109 | 110 | fn setup(test_dir: &str) { 111 | clean_output_dir(test_dir); 112 | } 113 | 114 | const SLATEPACK_DIR: &'static str = "target/test_output/slatepack"; 115 | 116 | #[test] 117 | fn pathbuf_get_file_contents() { 118 | global::set_local_chain_type(global::ChainTypes::AutomatedTesting); 119 | setup(SLATEPACK_DIR); 120 | 121 | fs::create_dir_all(SLATEPACK_DIR).unwrap(); 122 | let sp_path = PathBuf::from(SLATEPACK_DIR).join("pack_file"); 123 | 124 | // set Slatepack file to minimum allowable size 125 | { 126 | let f = File::create(sp_path.clone()).unwrap(); 127 | f.set_len(slatepack::min_size()).unwrap(); 128 | } 129 | 130 | let args = slatepack::SlatepackerArgs { 131 | sender: None, 132 | recipients: vec![], 133 | dec_key: None, 134 | }; 135 | let packer = Slatepacker::new(args); 136 | 137 | let mut pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true); 138 | assert!(pack_path.get_slatepack_file_contents().is_ok()); 139 | 140 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false); 141 | assert!(pack_path.get_slatepack_file_contents().is_ok()); 142 | 143 | // set Slatepack file to maximum allowable size 144 | { 145 | let f = File::create(sp_path.clone()).unwrap(); 146 | f.set_len(slatepack::max_size()).unwrap(); 147 | } 148 | 149 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true); 150 | assert!(pack_path.get_slatepack_file_contents().is_ok()); 151 | 152 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false); 153 | assert!(pack_path.get_slatepack_file_contents().is_ok()); 154 | 155 | // set Slatepack file below minimum allowable size 156 | { 157 | let f = File::create(sp_path.clone()).unwrap(); 158 | f.set_len(slatepack::min_size() - 1).unwrap(); 159 | } 160 | 161 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true); 162 | assert!(pack_path.get_slatepack_file_contents().is_err()); 163 | 164 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false); 165 | assert!(pack_path.get_slatepack_file_contents().is_err()); 166 | 167 | // set Slatepack file above maximum allowable size 168 | { 169 | let f = File::create(sp_path.clone()).unwrap(); 170 | f.set_len(slatepack::max_size() + 1).unwrap(); 171 | } 172 | 173 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true); 174 | assert!(pack_path.get_slatepack_file_contents().is_err()); 175 | 176 | pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false); 177 | assert!(pack_path.get_slatepack_file_contents().is_err()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /controller/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Implementation specific error types 16 | use crate::api; 17 | use crate::core::core::transaction; 18 | use crate::core::libtx; 19 | use crate::impls; 20 | use crate::keychain; 21 | use crate::libwallet; 22 | use failure::{Backtrace, Context, Fail}; 23 | use std::env; 24 | use std::fmt::{self, Display}; 25 | 26 | /// Error definition 27 | #[derive(Debug)] 28 | pub struct Error { 29 | pub inner: Context, 30 | } 31 | 32 | /// Wallet errors, mostly wrappers around underlying crypto or I/O errors. 33 | #[derive(Clone, Eq, PartialEq, Debug, Fail)] 34 | pub enum ErrorKind { 35 | /// LibTX Error 36 | #[fail(display = "LibTx Error")] 37 | LibTX(libtx::ErrorKind), 38 | 39 | /// Impls error 40 | #[fail(display = "Impls Error")] 41 | Impls(impls::ErrorKind), 42 | 43 | /// LibWallet Error 44 | #[fail(display = "LibWallet Error: {}", _1)] 45 | LibWallet(libwallet::ErrorKind, String), 46 | 47 | /// Keychain error 48 | #[fail(display = "Keychain error")] 49 | Keychain(keychain::Error), 50 | 51 | /// Transaction Error 52 | #[fail(display = "Transaction error")] 53 | Transaction(transaction::Error), 54 | 55 | /// Secp Error 56 | #[fail(display = "Secp error")] 57 | Secp, 58 | 59 | /// Filewallet error 60 | #[fail(display = "Wallet data error: {}", _0)] 61 | FileWallet(&'static str), 62 | 63 | /// Error when formatting json 64 | #[fail(display = "IO error")] 65 | IO, 66 | 67 | /// Error when formatting json 68 | #[fail(display = "Serde JSON error")] 69 | Format, 70 | 71 | /// Error when contacting a node through its API 72 | #[fail(display = "Node API error")] 73 | Node(api::ErrorKind), 74 | 75 | /// Error originating from hyper. 76 | #[fail(display = "Hyper error")] 77 | Hyper, 78 | 79 | /// Error originating from hyper uri parsing. 80 | #[fail(display = "Uri parsing error")] 81 | Uri, 82 | 83 | /// Attempt to use duplicate transaction id in separate transactions 84 | #[fail(display = "Duplicate transaction ID error")] 85 | DuplicateTransactionId, 86 | 87 | /// Wallet seed already exists 88 | #[fail(display = "Wallet seed file exists: {}", _0)] 89 | WalletSeedExists(String), 90 | 91 | /// Wallet seed doesn't exist 92 | #[fail(display = "Wallet seed doesn't exist error")] 93 | WalletSeedDoesntExist, 94 | 95 | /// Enc/Decryption Error 96 | #[fail(display = "Enc/Decryption error (check password?)")] 97 | Encryption, 98 | 99 | /// BIP 39 word list 100 | #[fail(display = "BIP39 Mnemonic (word list) Error")] 101 | Mnemonic, 102 | 103 | /// Command line argument error 104 | #[fail(display = "{}", _0)] 105 | ArgumentError(String), 106 | 107 | /// Other 108 | #[fail(display = "Listener Startup Error")] 109 | ListenerError, 110 | 111 | /// Other 112 | #[fail(display = "Generic error: {}", _0)] 113 | GenericError(String), 114 | } 115 | 116 | impl Fail for Error { 117 | fn cause(&self) -> Option<&dyn Fail> { 118 | self.inner.cause() 119 | } 120 | 121 | fn backtrace(&self) -> Option<&Backtrace> { 122 | self.inner.backtrace() 123 | } 124 | } 125 | 126 | impl Display for Error { 127 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 128 | let show_bt = match env::var("RUST_BACKTRACE") { 129 | Ok(r) => { 130 | if r == "1" { 131 | true 132 | } else { 133 | false 134 | } 135 | } 136 | Err(_) => false, 137 | }; 138 | let backtrace = match self.backtrace() { 139 | Some(b) => format!("{}", b), 140 | None => String::from("Unknown"), 141 | }; 142 | let inner_output = format!("{}", self.inner,); 143 | let backtrace_output = format!("\nBacktrace: {}", backtrace); 144 | let mut output = inner_output.clone(); 145 | if show_bt { 146 | output.push_str(&backtrace_output); 147 | } 148 | Display::fmt(&output, f) 149 | } 150 | } 151 | 152 | impl Error { 153 | /// get kind 154 | pub fn kind(&self) -> ErrorKind { 155 | self.inner.get_context().clone() 156 | } 157 | /// get cause 158 | pub fn cause(&self) -> Option<&dyn Fail> { 159 | self.inner.cause() 160 | } 161 | /// get backtrace 162 | pub fn backtrace(&self) -> Option<&Backtrace> { 163 | self.inner.backtrace() 164 | } 165 | } 166 | 167 | impl From for Error { 168 | fn from(kind: ErrorKind) -> Error { 169 | Error { 170 | inner: Context::new(kind), 171 | } 172 | } 173 | } 174 | 175 | impl From> for Error { 176 | fn from(inner: Context) -> Error { 177 | Error { inner: inner } 178 | } 179 | } 180 | 181 | impl From for Error { 182 | fn from(error: api::Error) -> Error { 183 | Error { 184 | inner: Context::new(ErrorKind::Node(error.kind().clone())), 185 | } 186 | } 187 | } 188 | 189 | impl From for Error { 190 | fn from(error: keychain::Error) -> Error { 191 | Error { 192 | inner: Context::new(ErrorKind::Keychain(error)), 193 | } 194 | } 195 | } 196 | 197 | impl From for Error { 198 | fn from(error: transaction::Error) -> Error { 199 | Error { 200 | inner: Context::new(ErrorKind::Transaction(error)), 201 | } 202 | } 203 | } 204 | 205 | impl From for Error { 206 | fn from(error: libwallet::Error) -> Error { 207 | Error { 208 | inner: Context::new(ErrorKind::LibWallet(error.kind(), format!("{}", error))), 209 | } 210 | } 211 | } 212 | 213 | impl From for Error { 214 | fn from(error: libtx::Error) -> Error { 215 | Error { 216 | inner: Context::new(ErrorKind::LibTX(error.kind())), 217 | } 218 | } 219 | } 220 | 221 | impl From for Error { 222 | fn from(error: impls::Error) -> Error { 223 | Error { 224 | inner: Context::new(ErrorKind::Impls(error.kind())), 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /integration/tests/dandelion.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[macro_use] 16 | extern crate log; 17 | 18 | mod framework; 19 | 20 | use self::util::Mutex; 21 | use crate::framework::{LocalServerContainer, LocalServerContainerConfig}; 22 | use grin_core as core; 23 | use grin_util as util; 24 | use std::sync::Arc; 25 | use std::{thread, time}; 26 | 27 | /// Start 1 node mining, 1 non mining node and two wallets. 28 | /// Then send a transaction from one wallet to another and propagate it a stem 29 | /// transaction but without stem relay and check if the transaction is still 30 | /// broadcasted. 31 | #[test] 32 | #[ignore] 33 | fn test_dandelion_timeout() { 34 | let test_name_dir = "test_dandelion_timeout"; 35 | core::global::set_local_chain_type(core::global::ChainTypes::AutomatedTesting); 36 | framework::clean_all_output(test_name_dir); 37 | let mut log_config = util::LoggingConfig::default(); 38 | //log_config.stdout_log_level = util::LogLevel::Trace; 39 | log_config.stdout_log_level = util::LogLevel::Info; 40 | //init_logger(Some(log_config)); 41 | util::init_test_logger(); 42 | 43 | // Run a separate coinbase wallet for coinbase transactions 44 | let mut coinbase_config = LocalServerContainerConfig::default(); 45 | coinbase_config.name = String::from("coinbase_wallet"); 46 | coinbase_config.wallet_validating_node_url = String::from("http://127.0.0.1:30001"); 47 | coinbase_config.wallet_port = 10002; 48 | let coinbase_wallet = Arc::new(Mutex::new( 49 | LocalServerContainer::new(coinbase_config).unwrap(), 50 | )); 51 | let coinbase_wallet_config = { coinbase_wallet.lock().wallet_config.clone() }; 52 | 53 | let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config); 54 | 55 | let _ = thread::spawn(move || { 56 | let mut w = coinbase_wallet.lock(); 57 | w.run_wallet(0); 58 | }); 59 | 60 | let mut recp_config = LocalServerContainerConfig::default(); 61 | recp_config.name = String::from("target_wallet"); 62 | recp_config.wallet_validating_node_url = String::from("http://127.0.0.1:30001"); 63 | recp_config.wallet_port = 20002; 64 | let target_wallet = Arc::new(Mutex::new(LocalServerContainer::new(recp_config).unwrap())); 65 | let target_wallet_cloned = target_wallet.clone(); 66 | let recp_wallet_config = { target_wallet.lock().wallet_config.clone() }; 67 | 68 | let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config); 69 | //Start up a second wallet, to receive 70 | let _ = thread::spawn(move || { 71 | let mut w = target_wallet_cloned.lock(); 72 | w.run_wallet(0); 73 | }); 74 | 75 | // Spawn server and let it run for a bit 76 | let mut server_one_config = LocalServerContainerConfig::default(); 77 | server_one_config.name = String::from("server_one"); 78 | server_one_config.p2p_server_port = 30000; 79 | server_one_config.api_server_port = 30001; 80 | server_one_config.start_miner = true; 81 | server_one_config.start_wallet = false; 82 | server_one_config.is_seeding = false; 83 | server_one_config.coinbase_wallet_address = 84 | String::from(format!("http://{}:{}", server_one_config.base_addr, 10002)); 85 | let mut server_one = LocalServerContainer::new(server_one_config).unwrap(); 86 | 87 | let mut server_two_config = LocalServerContainerConfig::default(); 88 | server_two_config.name = String::from("server_two"); 89 | server_two_config.p2p_server_port = 40000; 90 | server_two_config.api_server_port = 40001; 91 | server_two_config.start_miner = false; 92 | server_two_config.start_wallet = false; 93 | server_two_config.is_seeding = true; 94 | let mut server_two = LocalServerContainer::new(server_two_config.clone()).unwrap(); 95 | 96 | server_one.add_peer(format!( 97 | "{}:{}", 98 | server_two_config.base_addr, server_two_config.p2p_server_port 99 | )); 100 | 101 | // Spawn servers and let them run for a bit 102 | let _ = thread::spawn(move || { 103 | server_two.run_server(120); 104 | }); 105 | 106 | // Wait for the first server to start 107 | thread::sleep(time::Duration::from_millis(5000)); 108 | 109 | let _ = thread::spawn(move || { 110 | server_one.run_server(120); 111 | }); 112 | 113 | // Let them do a handshake and properly update their peer relay 114 | thread::sleep(time::Duration::from_millis(30000)); 115 | 116 | //Wait until we have some funds to send 117 | let mut coinbase_info = 118 | LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); 119 | let mut slept_time = 0; 120 | while coinbase_info.amount_currently_spendable < 100000000000 { 121 | thread::sleep(time::Duration::from_millis(500)); 122 | slept_time += 500; 123 | if slept_time > 10000 { 124 | panic!("Coinbase not confirming in time"); 125 | } 126 | coinbase_info = 127 | LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); 128 | } 129 | 130 | warn!("Sending 50 Grins to recipient wallet"); 131 | 132 | // Sending stem transaction 133 | LocalServerContainer::send_amount_to( 134 | &coinbase_wallet_config, 135 | "50.00", 136 | 1, 137 | "not_all", 138 | "http://127.0.0.1:20002", 139 | false, 140 | ); 141 | 142 | let coinbase_info = 143 | LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); 144 | println!("Coinbase wallet info: {:?}", coinbase_info); 145 | 146 | let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); 147 | 148 | // The transaction should be waiting in the node stempool thus cannot be mined. 149 | println!("Recipient wallet info: {:?}", recipient_info); 150 | assert!(recipient_info.amount_awaiting_confirmation == 50000000000); 151 | 152 | // Wait for stem timeout 153 | thread::sleep(time::Duration::from_millis(35000)); 154 | println!("Recipient wallet info: {:?}", recipient_info); 155 | let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); 156 | assert!(recipient_info.amount_currently_spendable == 50000000000); 157 | } 158 | -------------------------------------------------------------------------------- /controller/tests/ttl_cutoff.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! tests ttl_cutoff blocks 15 | #[macro_use] 16 | extern crate log; 17 | extern crate grin_wallet_controller as wallet; 18 | extern crate grin_wallet_impls as impls; 19 | extern crate grin_wallet_util; 20 | 21 | use grin_wallet_libwallet as libwallet; 22 | use impls::test_framework::{self, LocalWalletClient}; 23 | use libwallet::{InitTxArgs, Slate, TxLogEntryType}; 24 | use std::sync::atomic::Ordering; 25 | use std::thread; 26 | use std::time::Duration; 27 | 28 | #[macro_use] 29 | mod common; 30 | use common::{clean_output_dir, create_wallet_proxy, setup}; 31 | 32 | /// Test cutoff block times 33 | fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { 34 | // Create a new proxy to simulate server and wallet responses 35 | let mut wallet_proxy = create_wallet_proxy(test_dir); 36 | let chain = wallet_proxy.chain.clone(); 37 | let stopper = wallet_proxy.running.clone(); 38 | 39 | create_wallet_and_add!( 40 | client1, 41 | wallet1, 42 | mask1_i, 43 | test_dir, 44 | "wallet1", 45 | None, 46 | &mut wallet_proxy, 47 | false 48 | ); 49 | 50 | let mask1 = (&mask1_i).as_ref(); 51 | 52 | create_wallet_and_add!( 53 | client2, 54 | wallet2, 55 | mask2_i, 56 | test_dir, 57 | "wallet2", 58 | None, 59 | &mut wallet_proxy, 60 | false 61 | ); 62 | 63 | let mask2 = (&mask2_i).as_ref(); 64 | 65 | // Set the wallet proxy listener running 66 | thread::spawn(move || { 67 | if let Err(e) = wallet_proxy.run() { 68 | error!("Wallet Proxy error: {}", e); 69 | } 70 | }); 71 | 72 | // few values to keep things shorter 73 | 74 | // Do some mining 75 | let bh = 10u64; 76 | let _ = 77 | test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); 78 | 79 | let amount = 60_000_000_000; 80 | let mut slate = Slate::blank(1, false); 81 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { 82 | // note this will increment the block count as part of the transaction "Posting" 83 | let args = InitTxArgs { 84 | src_acct_name: None, 85 | amount: amount, 86 | minimum_confirmations: 2, 87 | max_outputs: 500, 88 | num_change_outputs: 1, 89 | selection_strategy_is_use_all: true, 90 | ttl_blocks: Some(2), 91 | ..Default::default() 92 | }; 93 | let slate_i = sender_api.init_send_tx(m, args)?; 94 | 95 | slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; 96 | sender_api.tx_lock_outputs(m, &slate)?; 97 | 98 | let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 99 | let tx = txs[0].clone(); 100 | 101 | assert_eq!(tx.ttl_cutoff_height, Some(12)); 102 | Ok(()) 103 | })?; 104 | 105 | // Now mine past the block, and check again. Transaction should be gone. 106 | let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); 107 | 108 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { 109 | let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 110 | let tx = txs[0].clone(); 111 | 112 | assert_eq!(tx.ttl_cutoff_height, Some(12)); 113 | assert!(tx.tx_type == TxLogEntryType::TxSentCancelled); 114 | Ok(()) 115 | })?; 116 | 117 | // Should also be gone in wallet 2, and output gone 118 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { 119 | let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 120 | let tx = txs[0].clone(); 121 | let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1; 122 | assert_eq!(outputs.len(), 0); 123 | 124 | assert_eq!(tx.ttl_cutoff_height, Some(12)); 125 | assert!(tx.tx_type == TxLogEntryType::TxReceivedCancelled); 126 | Ok(()) 127 | })?; 128 | 129 | // try again, except try and send off the transaction for completion beyond the expiry 130 | let mut slate = Slate::blank(1, false); 131 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { 132 | // note this will increment the block count as part of the transaction "Posting" 133 | let args = InitTxArgs { 134 | src_acct_name: None, 135 | amount: amount, 136 | minimum_confirmations: 2, 137 | max_outputs: 500, 138 | num_change_outputs: 1, 139 | selection_strategy_is_use_all: true, 140 | ttl_blocks: Some(2), 141 | ..Default::default() 142 | }; 143 | let slate_i = sender_api.init_send_tx(m, args)?; 144 | sender_api.tx_lock_outputs(m, &slate_i)?; 145 | slate = slate_i; 146 | 147 | let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 148 | let tx = txs[0].clone(); 149 | 150 | assert_eq!(tx.ttl_cutoff_height, Some(14)); 151 | Ok(()) 152 | })?; 153 | 154 | // Mine past the ttl block and try to send 155 | let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); 156 | 157 | // Wallet 2 will need to have updated past the TTL 158 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { 159 | let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; 160 | Ok(()) 161 | })?; 162 | 163 | // And when wallet 1 sends, should be rejected 164 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |_sender_api, _m| { 165 | let res = client1.send_tx_slate_direct("wallet2", &slate); 166 | println!("Send after TTL result is: {:?}", res); 167 | assert!(res.is_err()); 168 | Ok(()) 169 | })?; 170 | 171 | // let logging finish 172 | stopper.store(false, Ordering::Relaxed); 173 | thread::sleep(Duration::from_millis(200)); 174 | Ok(()) 175 | } 176 | 177 | #[test] 178 | fn ttl_cutoff() { 179 | let test_dir = "test_output/ttl_cutoff"; 180 | setup(test_dir); 181 | if let Err(e) = ttl_cutoff_test_impl(test_dir) { 182 | panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 183 | } 184 | clean_output_dir(test_dir); 185 | } 186 | -------------------------------------------------------------------------------- /config/src/comments.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Comments for configuration + injection into output .toml 16 | use std::collections::HashMap; 17 | 18 | /// maps entries to Comments that should precede them 19 | fn comments() -> HashMap { 20 | let mut retval = HashMap::new(); 21 | 22 | retval.insert( 23 | "[wallet]".to_string(), 24 | " 25 | ######################################### 26 | ### WALLET CONFIGURATION ### 27 | ######################################### 28 | " 29 | .to_string(), 30 | ); 31 | 32 | retval.insert( 33 | "api_listen_interface".to_string(), 34 | " 35 | #host IP for wallet listener, change to \"0.0.0.0\" to receive grins 36 | " 37 | .to_string(), 38 | ); 39 | 40 | retval.insert( 41 | "api_listen_port".to_string(), 42 | " 43 | #path of TLS certificate file, self-signed certificates are not supported 44 | #tls_certificate_file = \"\" 45 | #private key for the TLS certificate 46 | #tls_certificate_key = \"\" 47 | 48 | #port for wallet listener 49 | " 50 | .to_string(), 51 | ); 52 | 53 | retval.insert( 54 | "owner_api_listen_port".to_string(), 55 | " 56 | #port for wallet owner api 57 | " 58 | .to_string(), 59 | ); 60 | 61 | retval.insert( 62 | "api_secret_path".to_string(), 63 | " 64 | #path of the secret token used by the API to authenticate the calls 65 | #comment it to disable basic auth 66 | " 67 | .to_string(), 68 | ); 69 | retval.insert( 70 | "check_node_api_http_addr".to_string(), 71 | " 72 | #where the wallet should find a running node 73 | " 74 | .to_string(), 75 | ); 76 | retval.insert( 77 | "node_api_secret_path".to_string(), 78 | " 79 | #location of the node api secret for basic auth on the Grin API 80 | " 81 | .to_string(), 82 | ); 83 | retval.insert( 84 | "owner_api_include_foreign".to_string(), 85 | " 86 | #include the foreign API endpoints on the same port as the owner 87 | #API. Useful for networking environments like AWS ECS that make 88 | #it difficult to access multiple ports on a single service. 89 | " 90 | .to_string(), 91 | ); 92 | retval.insert( 93 | "data_file_dir".to_string(), 94 | " 95 | #where to find wallet files (seed, data, etc) 96 | " 97 | .to_string(), 98 | ); 99 | retval.insert( 100 | "no_commit_cache".to_string(), 101 | " 102 | #If true, don't store calculated commits in the database 103 | #better privacy, but at a performance cost of having to 104 | #re-calculate commits every time they're used 105 | " 106 | .to_string(), 107 | ); 108 | retval.insert( 109 | "dark_background_color_scheme".to_string(), 110 | " 111 | #Whether to use the black background color scheme for command line 112 | " 113 | .to_string(), 114 | ); 115 | retval.insert( 116 | "keybase_notify_ttl".to_string(), 117 | " 118 | #The exploding lifetime for keybase notification on coins received. 119 | #Unit: Minute. Default value 1440 minutes for one day. 120 | #Refer to https://keybase.io/blog/keybase-exploding-messages for detail. 121 | #To disable this notification, set it as 0. 122 | " 123 | .to_string(), 124 | ); 125 | 126 | retval.insert( 127 | "[logging]".to_string(), 128 | " 129 | ######################################### 130 | ### LOGGING CONFIGURATION ### 131 | ######################################### 132 | " 133 | .to_string(), 134 | ); 135 | 136 | retval.insert( 137 | "log_to_stdout".to_string(), 138 | " 139 | #whether to log to stdout 140 | " 141 | .to_string(), 142 | ); 143 | 144 | retval.insert( 145 | "stdout_log_level".to_string(), 146 | " 147 | #log level for stdout: Error, Warning, Info, Debug, Trace 148 | " 149 | .to_string(), 150 | ); 151 | 152 | retval.insert( 153 | "log_to_file".to_string(), 154 | " 155 | #whether to log to a file 156 | " 157 | .to_string(), 158 | ); 159 | 160 | retval.insert( 161 | "file_log_level".to_string(), 162 | " 163 | #log level for file: Error, Warning, Info, Debug, Trace 164 | " 165 | .to_string(), 166 | ); 167 | 168 | retval.insert( 169 | "log_file_path".to_string(), 170 | " 171 | #log file path 172 | " 173 | .to_string(), 174 | ); 175 | 176 | retval.insert( 177 | "log_file_append".to_string(), 178 | " 179 | #whether to append to the log file (true), or replace it on every run (false) 180 | " 181 | .to_string(), 182 | ); 183 | 184 | retval.insert( 185 | "log_max_size".to_string(), 186 | " 187 | #maximum log file size in bytes before performing log rotation 188 | #comment it to disable log rotation 189 | " 190 | .to_string(), 191 | ); 192 | 193 | retval.insert( 194 | "[tor]".to_string(), 195 | " 196 | ######################################### 197 | ### TOR CONFIGURATION (Experimental) ### 198 | ######################################### 199 | " 200 | .to_string(), 201 | ); 202 | 203 | retval.insert( 204 | "use_tor_listener".to_string(), 205 | " 206 | #Whether to start tor listener on listener startup (default true) 207 | " 208 | .to_string(), 209 | ); 210 | 211 | retval.insert( 212 | "socks_proxy_addr".to_string(), 213 | " 214 | #Address of the running TOR (SOCKS) server 215 | " 216 | .to_string(), 217 | ); 218 | 219 | retval.insert( 220 | "socks_proxy_addr".to_string(), 221 | " 222 | # TOR (SOCKS) proxy server address 223 | " 224 | .to_string(), 225 | ); 226 | 227 | retval.insert( 228 | "send_config_dir".to_string(), 229 | " 230 | #Directory to output TOR configuration to when sending 231 | " 232 | .to_string(), 233 | ); 234 | 235 | retval 236 | } 237 | 238 | fn get_key(line: &str) -> String { 239 | if line.contains('[') && line.contains(']') { 240 | line.to_owned() 241 | } else if line.contains('=') { 242 | line.split('=').collect::>()[0].trim().to_owned() 243 | } else { 244 | "NOT_FOUND".to_owned() 245 | } 246 | } 247 | 248 | pub fn insert_comments(orig: String) -> String { 249 | let comments = comments(); 250 | let lines: Vec<&str> = orig.split('\n').collect(); 251 | let mut out_lines = vec![]; 252 | for l in lines { 253 | let key = get_key(l); 254 | if let Some(v) = comments.get(&key) { 255 | out_lines.push(v.to_owned()); 256 | } 257 | out_lines.push(l.to_owned()); 258 | out_lines.push("\n".to_owned()); 259 | } 260 | let mut ret_val = String::from(""); 261 | for l in out_lines { 262 | ret_val.push_str(&l); 263 | } 264 | ret_val 265 | } 266 | -------------------------------------------------------------------------------- /util/src/ov3.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::grin_util::from_hex; 16 | use data_encoding::BASE32; 17 | use ed25519_dalek::PublicKey as DalekPublicKey; 18 | use ed25519_dalek::SecretKey as DalekSecretKey; 19 | use sha3::{Digest, Sha3_256}; 20 | use std::convert::TryFrom; 21 | use std::fmt; 22 | 23 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 24 | /// OnionV3 Address Errors 25 | pub enum OnionV3Error { 26 | /// Error decoding an address from a string 27 | AddressDecoding(String), 28 | /// Error with given private key 29 | InvalidPrivateKey(String), 30 | } 31 | 32 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 33 | /// Struct to hold an onion V3 address, represented internally as a raw 34 | /// ed25519 public key 35 | pub struct OnionV3Address([u8; 32]); 36 | 37 | impl OnionV3Address { 38 | /// from bytes 39 | pub fn from_bytes(bytes: [u8; 32]) -> Self { 40 | OnionV3Address(bytes) 41 | } 42 | 43 | /// as bytes 44 | pub fn as_bytes(&self) -> &[u8; 32] { 45 | &self.0 46 | } 47 | 48 | /// populate from a private key 49 | pub fn from_private(key: &[u8; 32]) -> Result { 50 | let d_skey = match DalekSecretKey::from_bytes(key) { 51 | Ok(k) => k, 52 | Err(e) => { 53 | return Err(OnionV3Error::InvalidPrivateKey(format!( 54 | "Unable to create public key: {}", 55 | e 56 | ))); 57 | } 58 | }; 59 | let d_pub_key: DalekPublicKey = (&d_skey).into(); 60 | Ok(OnionV3Address(*d_pub_key.as_bytes())) 61 | } 62 | 63 | /// return dalek public key 64 | pub fn to_ed25519(&self) -> Result { 65 | let d_skey = match DalekPublicKey::from_bytes(&self.0) { 66 | Ok(k) => k, 67 | Err(e) => { 68 | return Err(OnionV3Error::InvalidPrivateKey(format!( 69 | "Unable to create dalek public key: {}", 70 | e 71 | ))); 72 | } 73 | }; 74 | Ok(d_skey) 75 | } 76 | 77 | /// Return as onion v3 address string 78 | pub fn to_ov3_str(&self) -> String { 79 | // calculate checksum 80 | let mut hasher = Sha3_256::new(); 81 | hasher.input(b".onion checksum"); 82 | hasher.input(self.0); 83 | hasher.input([0x03u8]); 84 | let checksum = hasher.result(); 85 | 86 | let mut address_bytes = self.0.to_vec(); 87 | address_bytes.push(checksum[0]); 88 | address_bytes.push(checksum[1]); 89 | address_bytes.push(0x03u8); 90 | 91 | let ret = BASE32.encode(&address_bytes); 92 | ret.to_lowercase() 93 | } 94 | 95 | /// return as http url 96 | pub fn to_http_str(&self) -> String { 97 | format!("http://{}.onion", self.to_ov3_str()) 98 | } 99 | } 100 | 101 | impl fmt::Display for OnionV3Address { 102 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 103 | write!(f, "{}", self.to_ov3_str()) 104 | } 105 | } 106 | 107 | impl TryFrom<&str> for OnionV3Address { 108 | type Error = OnionV3Error; 109 | 110 | fn try_from(input: &str) -> Result { 111 | // First attempt to decode a pubkey from hex 112 | if let Ok(b) = from_hex(input) { 113 | if b.len() == 32 { 114 | let mut retval = OnionV3Address([0; 32]); 115 | retval.0.copy_from_slice(&b[0..32]); 116 | return Ok(retval); 117 | } else { 118 | return Err(OnionV3Error::AddressDecoding( 119 | "(Interpreted as Hex String) Public key is wrong length".to_owned(), 120 | )); 121 | } 122 | }; 123 | 124 | // Otherwise try to parse as onion V3 address 125 | let mut input = input.to_uppercase(); 126 | if input.starts_with("HTTP://") || input.starts_with("HTTPS://") { 127 | input = input.replace("HTTP://", ""); 128 | input = input.replace("HTTPS://", ""); 129 | } 130 | if input.ends_with(".ONION") { 131 | input = input.replace(".ONION", ""); 132 | } 133 | let orig_address_raw = input.clone(); 134 | // for now, just check input is the right length and try and decode from base32 135 | if input.len() != 56 { 136 | return Err(OnionV3Error::AddressDecoding( 137 | "(Interpreted as Base32 String) Input address is wrong length".to_owned(), 138 | )); 139 | } 140 | let address = match BASE32.decode(input.as_bytes()) { 141 | Ok(a) => a, 142 | Err(_) => { 143 | return Err(OnionV3Error::AddressDecoding( 144 | "(Interpreted as Base32 String) Input address is not base 32".to_owned(), 145 | )); 146 | } 147 | }; 148 | 149 | let mut retval = OnionV3Address([0; 32]); 150 | retval.0.copy_from_slice(&address[0..32]); 151 | 152 | let test_v3 = retval.to_ov3_str(); 153 | if test_v3.to_uppercase() != orig_address_raw.to_uppercase() { 154 | return Err(OnionV3Error::AddressDecoding( 155 | "(Interpreted as Base32 String) Provided onion V3 address is invalid (no match)" 156 | .to_owned(), 157 | )); 158 | } 159 | 160 | Ok(retval) 161 | } 162 | } 163 | 164 | #[cfg(test)] 165 | mod test { 166 | use super::*; 167 | use std::convert::TryInto; 168 | 169 | #[test] 170 | fn onion_v3() -> Result<(), OnionV3Error> { 171 | let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"; 172 | let onion_address: OnionV3Address = onion_address_str.try_into()?; 173 | 174 | println!("Onion address: {:?}", onion_address); 175 | let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; 176 | let onion_address_2: OnionV3Address = raw_pubkey_str.try_into()?; 177 | 178 | assert_eq!(onion_address, onion_address_2); 179 | 180 | // invalid hex string, should be interpreted as base32 and fail 181 | let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bx"; 182 | let ret: Result = raw_pubkey_str.try_into(); 183 | assert!(ret.is_err()); 184 | 185 | // wrong length hex string, should be interpreted as base32 and fail 186 | let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bbff"; 187 | let ret: Result = raw_pubkey_str.try_into(); 188 | assert!(ret.is_err()); 189 | 190 | // wrong length ov3 string 191 | let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyidx"; 192 | let ret: Result = onion_address_str.try_into(); 193 | assert!(ret.is_err()); 194 | 195 | // not base 32 ov3 string 196 | let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyi-"; 197 | let ret: Result = onion_address_str.try_into(); 198 | assert!(ret.is_err()); 199 | 200 | Ok(()) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /integration/tests/stratum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[macro_use] 16 | extern crate log; 17 | 18 | mod framework; 19 | 20 | use self::core::global::{self, ChainTypes}; 21 | use crate::framework::{config, stratum_config}; 22 | use bufstream::BufStream; 23 | use grin_core as core; 24 | use grin_servers as servers; 25 | use grin_util as util; 26 | use grin_util::{Mutex, StopState}; 27 | use serde_json::Value; 28 | use std::io::prelude::{BufRead, Write}; 29 | use std::net::TcpStream; 30 | use std::process; 31 | use std::sync::Arc; 32 | use std::{thread, time}; 33 | 34 | // Create a grin server, and a stratum server. 35 | // Simulate a few JSONRpc requests and verify the results. 36 | // Validate disconnected workers 37 | // Validate broadcasting new jobs 38 | #[test] 39 | fn basic_stratum_server() { 40 | util::init_test_logger(); 41 | global::set_local_chain_type(ChainTypes::AutomatedTesting); 42 | 43 | let test_name_dir = "stratum_server"; 44 | framework::clean_all_output(test_name_dir); 45 | 46 | // Create a server 47 | let s = servers::Server::new(config(4000, test_name_dir, 0)).unwrap(); 48 | 49 | // Get mining config with stratumserver enabled 50 | let mut stratum_cfg = stratum_config(); 51 | stratum_cfg.burn_reward = true; 52 | stratum_cfg.attempt_time_per_block = 999; 53 | stratum_cfg.enable_stratum_server = Some(true); 54 | stratum_cfg.stratum_server_addr = Some(String::from("127.0.0.1:11101")); 55 | 56 | // Start stratum server 57 | s.start_stratum_server(stratum_cfg); 58 | 59 | // Wait for stratum server to start and 60 | // Verify stratum server accepts connections 61 | loop { 62 | if let Ok(_stream) = TcpStream::connect("127.0.0.1:11101") { 63 | break; 64 | } else { 65 | thread::sleep(time::Duration::from_millis(500)); 66 | } 67 | // As this stream falls out of scope it will be disconnected 68 | } 69 | info!("stratum server connected"); 70 | 71 | // Create a few new worker connections 72 | let mut workers = vec![]; 73 | for _n in 0..5 { 74 | let w = TcpStream::connect("127.0.0.1:11101").unwrap(); 75 | w.set_nonblocking(true) 76 | .expect("Failed to set TcpStream to non-blocking"); 77 | let stream = BufStream::new(w); 78 | workers.push(stream); 79 | } 80 | assert!(workers.len() == 5); 81 | info!("workers length verification ok"); 82 | 83 | // Simulate a worker lost connection 84 | workers.remove(4); 85 | 86 | // Swallow the genesis block 87 | thread::sleep(time::Duration::from_secs(5)); // Wait for the server to broadcast 88 | let mut response = String::new(); 89 | for n in 0..workers.len() { 90 | let _result = workers[n].read_line(&mut response); 91 | } 92 | 93 | // Verify a few stratum JSONRpc commands 94 | // getjobtemplate - expected block template result 95 | let mut response = String::new(); 96 | let job_req = "{\"id\": \"Stratum\", \"jsonrpc\": \"2.0\", \"method\": \"getjobtemplate\"}\n"; 97 | workers[2].write(job_req.as_bytes()).unwrap(); 98 | workers[2].flush().unwrap(); 99 | thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply 100 | match workers[2].read_line(&mut response) { 101 | Ok(_) => { 102 | let r: Value = serde_json::from_str(&response).unwrap(); 103 | assert_eq!(r["error"], serde_json::Value::Null); 104 | assert_ne!(r["result"], serde_json::Value::Null); 105 | } 106 | Err(_e) => { 107 | assert!(false); 108 | } 109 | } 110 | info!("a few stratum JSONRpc commands verification ok"); 111 | 112 | // keepalive - expected "ok" result 113 | let mut response = String::new(); 114 | let job_req = "{\"id\":\"3\",\"jsonrpc\":\"2.0\",\"method\":\"keepalive\"}\n"; 115 | let ok_resp = "{\"id\":\"3\",\"jsonrpc\":\"2.0\",\"method\":\"keepalive\",\"result\":\"ok\",\"error\":null}\n"; 116 | workers[2].write(job_req.as_bytes()).unwrap(); 117 | workers[2].flush().unwrap(); 118 | thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply 119 | let _st = workers[2].read_line(&mut response); 120 | assert_eq!(response.as_str(), ok_resp); 121 | info!("keepalive test ok"); 122 | 123 | // "doesnotexist" - error expected 124 | let mut response = String::new(); 125 | let job_req = "{\"id\":\"4\",\"jsonrpc\":\"2.0\",\"method\":\"doesnotexist\"}\n"; 126 | let ok_resp = "{\"id\":\"4\",\"jsonrpc\":\"2.0\",\"method\":\"doesnotexist\",\"result\":null,\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}\n"; 127 | workers[3].write(job_req.as_bytes()).unwrap(); 128 | workers[3].flush().unwrap(); 129 | thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply 130 | let _st = workers[3].read_line(&mut response); 131 | assert_eq!(response.as_str(), ok_resp); 132 | info!("worker doesnotexist test ok"); 133 | 134 | // Verify stratum server and worker stats 135 | let stats = s.get_server_stats().unwrap(); 136 | assert_eq!(stats.stratum_stats.block_height, 1); // just 1 genesis block 137 | assert_eq!(stats.stratum_stats.num_workers, 4); // 5 - 1 = 4 138 | assert_eq!(stats.stratum_stats.worker_stats[5].is_connected, false); // worker was removed 139 | assert_eq!(stats.stratum_stats.worker_stats[1].is_connected, true); 140 | info!("stratum server and worker stats verification ok"); 141 | 142 | // Start mining blocks 143 | let stop = Arc::new(Mutex::new(StopState::new())); 144 | s.start_test_miner(None, stop.clone()); 145 | info!("test miner started"); 146 | 147 | // This test is supposed to complete in 3 seconds, 148 | // so let's set a timeout on 10s to avoid infinite waiting happened in Travis-CI. 149 | let _handler = thread::spawn(|| { 150 | thread::sleep(time::Duration::from_secs(10)); 151 | error!("basic_stratum_server test fail on timeout!"); 152 | thread::sleep(time::Duration::from_millis(100)); 153 | process::exit(1); 154 | }); 155 | 156 | // Simulate a worker lost connection 157 | workers.remove(1); 158 | 159 | // Wait for a few mined blocks 160 | thread::sleep(time::Duration::from_secs(3)); 161 | s.stop_test_miner(stop); 162 | 163 | // Verify blocks are being broadcast to workers 164 | let expected = String::from("job"); 165 | let mut jobtemplate = String::new(); 166 | let _st = workers[2].read_line(&mut jobtemplate); 167 | let job_template: Value = serde_json::from_str(&jobtemplate).unwrap(); 168 | assert_eq!(job_template["method"], expected); 169 | info!("blocks broadcasting to workers test ok"); 170 | 171 | // Verify stratum server and worker stats 172 | let stats = s.get_server_stats().unwrap(); 173 | assert_eq!(stats.stratum_stats.num_workers, 3); // 5 - 2 = 3 174 | assert_eq!(stats.stratum_stats.worker_stats[2].is_connected, false); // worker was removed 175 | assert_ne!(stats.stratum_stats.block_height, 1); 176 | info!("basic_stratum_server test done and ok."); 177 | } 178 | -------------------------------------------------------------------------------- /controller/tests/no_change.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Grin Developers 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //! Test sender transaction with no change output 15 | #[macro_use] 16 | extern crate log; 17 | extern crate grin_wallet_controller as wallet; 18 | extern crate grin_wallet_impls as impls; 19 | 20 | use grin_wallet_util::grin_core as core; 21 | 22 | use grin_wallet_libwallet as libwallet; 23 | use impls::test_framework::{self, LocalWalletClient}; 24 | use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; 25 | use std::sync::atomic::Ordering; 26 | use std::thread; 27 | use std::time::Duration; 28 | 29 | #[macro_use] 30 | mod common; 31 | use common::{clean_output_dir, create_wallet_proxy, setup}; 32 | 33 | fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { 34 | let mut wallet_proxy = create_wallet_proxy(test_dir); 35 | let chain = wallet_proxy.chain.clone(); 36 | let stopper = wallet_proxy.running.clone(); 37 | 38 | create_wallet_and_add!( 39 | client1, 40 | wallet1, 41 | mask1_i, 42 | test_dir, 43 | "wallet1", 44 | None, 45 | &mut wallet_proxy, 46 | false 47 | ); 48 | 49 | let mask1 = (&mask1_i).as_ref(); 50 | 51 | create_wallet_and_add!( 52 | client2, 53 | wallet2, 54 | mask2_i, 55 | test_dir, 56 | "wallet2", 57 | None, 58 | &mut wallet_proxy, 59 | false 60 | ); 61 | 62 | let mask2 = (&mask2_i).as_ref(); 63 | 64 | // Set the wallet proxy listener running 65 | thread::spawn(move || { 66 | if let Err(e) = wallet_proxy.run() { 67 | error!("Wallet Proxy error: {}", e); 68 | } 69 | }); 70 | 71 | // few values to keep things shorter 72 | let reward = core::consensus::REWARD_ADJUSTED; 73 | 74 | // Mine into wallet 1 75 | let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 4, false); 76 | let fee = core::libtx::tx_fee(1, 1, 1, 0, 0, 0, None); 77 | 78 | // send a single block's worth of transactions with minimal strategy 79 | let mut slate = Slate::blank(2, false); 80 | let mut stored_excess = None; 81 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 82 | let args = InitTxArgs { 83 | src_acct_name: None, 84 | amount: reward - fee, 85 | minimum_confirmations: 2, 86 | max_outputs: 500, 87 | num_change_outputs: 1, 88 | selection_strategy_is_use_all: false, 89 | ..Default::default() 90 | }; 91 | slate = api.init_send_tx(m, args)?; 92 | slate = client1.send_tx_slate_direct("wallet2", &slate)?; 93 | api.tx_lock_outputs(m, &slate)?; 94 | slate = api.finalize_tx(m, &slate)?; 95 | println!("Posted Slate: {:?}", slate); 96 | stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); 97 | api.post_tx(m, &slate, false)?; 98 | Ok(()) 99 | })?; 100 | 101 | // ensure stored excess is correct in both wallets 102 | // Wallet 1 calculated the excess with the full slate // Wallet 2 only had the excess provided by 103 | // wallet 1 104 | 105 | // Refresh and check transaction log for wallet 1 106 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 107 | let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; 108 | assert!(refreshed); 109 | let tx = txs[0].clone(); 110 | println!("SIMPLE SEND - SENDING WALLET"); 111 | println!("{:?}", tx); 112 | println!(); 113 | assert!(tx.confirmed); 114 | assert_eq!(stored_excess, tx.kernel_excess); 115 | Ok(()) 116 | })?; 117 | 118 | // Refresh and check transaction log for wallet 2 119 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { 120 | let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; 121 | assert!(refreshed); 122 | let tx = txs[0].clone(); 123 | println!("SIMPLE SEND - RECEIVING WALLET"); 124 | println!("{:?}", tx); 125 | println!(); 126 | assert!(tx.confirmed); 127 | assert_eq!(stored_excess, tx.kernel_excess); 128 | Ok(()) 129 | })?; 130 | 131 | // ensure invoice TX works as well with no change 132 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { 133 | // Wallet 2 inititates an invoice transaction, requesting payment 134 | let args = IssueInvoiceTxArgs { 135 | amount: reward - fee, 136 | ..Default::default() 137 | }; 138 | slate = api.issue_invoice_tx(m, args)?; 139 | Ok(()) 140 | })?; 141 | 142 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 143 | // Wallet 1 receives the invoice transaction 144 | let args = InitTxArgs { 145 | src_acct_name: None, 146 | amount: slate.amount, 147 | minimum_confirmations: 2, 148 | max_outputs: 500, 149 | num_change_outputs: 1, 150 | selection_strategy_is_use_all: false, 151 | ..Default::default() 152 | }; 153 | slate = api.process_invoice_tx(m, &slate, args)?; 154 | api.tx_lock_outputs(m, &slate)?; 155 | Ok(()) 156 | })?; 157 | 158 | // wallet 2 finalizes and posts 159 | wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { 160 | // Wallet 2 receives the invoice transaction 161 | slate = api.finalize_tx(&slate, false)?; 162 | Ok(()) 163 | })?; 164 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask1, None, |api, m| { 165 | println!("Invoice Posted TX: {}", slate); 166 | stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); 167 | api.post_tx(m, &slate, false)?; 168 | Ok(()) 169 | })?; 170 | 171 | // check wallet 2's version 172 | wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { 173 | let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; 174 | assert!(refreshed); 175 | for tx in txs { 176 | stored_excess = tx.kernel_excess; 177 | println!("Wallet 2: {:?}", tx); 178 | println!(); 179 | assert!(tx.confirmed); 180 | assert_eq!(stored_excess, tx.kernel_excess); 181 | } 182 | Ok(()) 183 | })?; 184 | 185 | // Refresh and check transaction log for wallet 1 186 | wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { 187 | let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; 188 | assert!(refreshed); 189 | for tx in txs { 190 | println!("Wallet 1: {:?}", tx); 191 | println!(); 192 | assert_eq!(stored_excess, tx.kernel_excess); 193 | assert!(tx.confirmed); 194 | } 195 | Ok(()) 196 | })?; 197 | 198 | // let logging finish 199 | stopper.store(false, Ordering::Relaxed); 200 | thread::sleep(Duration::from_millis(200)); 201 | Ok(()) 202 | } 203 | 204 | #[test] 205 | fn no_change() { 206 | let test_dir = "test_output/no_change"; 207 | setup(test_dir); 208 | if let Err(e) = no_change_test_impl(test_dir) { 209 | panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 210 | } 211 | clean_output_dir(test_dir); 212 | } 213 | --------------------------------------------------------------------------------