mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-15 15:43:49 +08:00
Chore/stacks signer cli cleanup (#3885)
* Cleanup documentation and CLI using clap and errors using thiserror Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co> * Do not enable host, contract, or private key to be specified if using the config option Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co> * Fix typo in comment Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co> --------- Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co>
This commit is contained in:
213
Cargo.lock
generated
213
Cargo.lock
generated
@@ -101,6 +101,54 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
@@ -460,6 +508,47 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||
|
||||
[[package]]
|
||||
name = "clarity"
|
||||
version = "0.0.1"
|
||||
@@ -502,6 +591,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.1.0"
|
||||
@@ -572,7 +667,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"clap 2.34.0",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
@@ -1173,6 +1268,12 @@ dependencies = [
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -2717,6 +2818,7 @@ dependencies = [
|
||||
name = "stacks-signer"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap 4.4.1",
|
||||
"clarity",
|
||||
"libsigner",
|
||||
"libstackerdb",
|
||||
@@ -2730,6 +2832,7 @@ dependencies = [
|
||||
"slog-json",
|
||||
"slog-term",
|
||||
"stacks-common",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -2842,6 +2945,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "stx-genesis"
|
||||
version = "0.1.0"
|
||||
@@ -3248,6 +3357,12 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.0.0-alpha.9"
|
||||
@@ -3490,13 +3605,13 @@ version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3505,7 +3620,16 @@ version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3514,13 +3638,28 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3529,42 +3668,84 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
|
||||
@@ -16,17 +16,19 @@ name = "stacks-signer"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clarity = { path = "../clarity" }
|
||||
clap = { version = "4.1.1", features = ["derive", "env"] }
|
||||
libsigner = { path = "../libsigner" }
|
||||
libstackerdb = { path = "../libstackerdb" }
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_stacker = "0.1"
|
||||
stacks-common = { path = "../stacks-common" }
|
||||
clarity = { path = "../clarity" }
|
||||
libstackerdb = { path = "../libstackerdb" }
|
||||
libsigner = { path = "../libsigner" }
|
||||
toml = "0.5.6"
|
||||
slog = { version = "2.5.2", features = [ "max_level_trace" ] }
|
||||
slog-term = "2.6.0"
|
||||
slog-json = { version = "2.3.0", optional = true }
|
||||
slog-term = "2.6.0"
|
||||
stacks-common = { path = "../stacks-common" }
|
||||
thiserror = "1.0"
|
||||
toml = "0.5.6"
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0"
|
||||
|
||||
94
stacks-signer/README.md
Normal file
94
stacks-signer/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# stacks-signer: Stacks Signer CLI
|
||||
|
||||
stacks-signer is a command-line interface (CLI) for executing DKG (Distributed Key Generation) rounds, signing transactions and blocks, and more within the Stacks blockchain ecosystem. This tool provides various subcommands to interact with the StackerDB, perform cryptographic operations, and manage configurations.
|
||||
|
||||
## Installation
|
||||
|
||||
To use stacks-signer, you need to build and install the Rust program. You can do this by following these steps:
|
||||
|
||||
1. **Clone the Repository**: Clone the stacks-signer repository from [GitHub](https://github.com/blockstack/stacks-blockchain).
|
||||
|
||||
```bash
|
||||
git clone https://github.com/blockstack/stacks-blockchain.git
|
||||
```
|
||||
|
||||
2. **Build the Program**: Change to the stacks-signer directory and build the program using `cargo`.
|
||||
|
||||
```bash
|
||||
cd stacks-signer
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
3. **Run the Program**: You can now run the stacks-signer CLI.
|
||||
|
||||
```bash
|
||||
./target/release/stacks-signer --help
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
You can provide configuration options such as the host, contract, and private key using a TOML file. Use the `--config` option to specify the path to the configuration file. Alternatively, you can provide the necessary options directly in the command line.
|
||||
|
||||
```bash
|
||||
./stacks-signer --config <config_file>
|
||||
```
|
||||
|
||||
- `--config`: Path to the TOML configuration file.
|
||||
|
||||
## Usage
|
||||
|
||||
The stacks-signer CLI provides the following subcommands:
|
||||
|
||||
### `get-chunk`
|
||||
|
||||
Retrieve a chunk from the StackerDB instance.
|
||||
|
||||
```bash
|
||||
./stacks-signer --config <config_file> get-chunk --slot_id <slot_id> --slot_version <slot_version>
|
||||
```
|
||||
|
||||
- `--host`: The stacks node host to connect to. Required if not using the --config option.
|
||||
- `--contract`: The contract ID of the StackerDB instance. Required if not using the --config option.
|
||||
- `--slot_id`: The slot ID to get.
|
||||
- `--slot_version`: The slot version to get.
|
||||
|
||||
### `get-latest-chunk`
|
||||
|
||||
Retrieve the latest chunk from the StackerDB instance.
|
||||
|
||||
```bash
|
||||
./stacks-signer --config <config_file> get-latest-chunk --slot_id <slot_id>
|
||||
```
|
||||
|
||||
- `--host`: The stacks node host to connect to. Required if not using the --config option.
|
||||
- `--contract`: The contract ID of the StackerDB instance. Required if not using the --config option.
|
||||
- `--slot_id`: The slot ID to get.
|
||||
|
||||
### `list-chunks`
|
||||
|
||||
List chunks from the StackerDB instance.
|
||||
|
||||
```bash
|
||||
./stacks-signer --config <config_file> list-chunks
|
||||
```
|
||||
|
||||
- `--host`: The stacks node host to connect to. Required if not using the --config option.
|
||||
- `--contract`: The contract ID of the StackerDB instance. Required if not using the --config option.
|
||||
|
||||
### `put-chunk`
|
||||
|
||||
Upload a chunk to the StackerDB instance.
|
||||
|
||||
```bash
|
||||
./stacks-signer --config <config_file> put-chunk --slot_id <slot_id> --slot_version <slot_version> [--data <data>]
|
||||
```
|
||||
|
||||
- `--host`: The stacks node host to connect to. Required if not using the --config option.
|
||||
- `--contract`: The contract ID of the StackerDB instance. Required if not using the --config option.
|
||||
- `--slot_id`: The slot ID to get.
|
||||
- `--slot_version`: The slot version to get.
|
||||
- `--data`: The data to upload. If you wish to pipe data using STDIN, use with '-'.
|
||||
|
||||
## License
|
||||
|
||||
This program is open-source software released under the terms of the GNU General Public License (GPL). You should have received a copy of the GNU General Public License along with this program.
|
||||
@@ -14,85 +14,80 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use toml;
|
||||
|
||||
use clarity::vm::types::QualifiedContractIdentifier;
|
||||
use serde::Deserialize;
|
||||
use stacks_common::types::chainstate::StacksPrivateKey;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fs,
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// An error occurred parsing the provided configuration
|
||||
pub enum ConfigError {
|
||||
NoSuchConfigFile(String),
|
||||
/// Error occurred reading config file
|
||||
#[error("{0}")]
|
||||
InvalidConfig(String),
|
||||
/// An error occurred parsing the TOML data
|
||||
#[error("{0}")]
|
||||
ParseError(String),
|
||||
/// A field was malformed
|
||||
#[error("identifier={0}, value={1}")]
|
||||
BadField(String, String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ConfigError::NoSuchConfigFile(ref s) => fmt::Display::fmt(s, f),
|
||||
ConfigError::ParseError(ref s) => fmt::Display::fmt(s, f),
|
||||
ConfigError::BadField(ref f1, ref f2) => {
|
||||
write!(f, "identifier={}, value={}", f1, f2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ConfigError {
|
||||
fn cause(&self) -> Option<&dyn error::Error> {
|
||||
match *self {
|
||||
ConfigError::NoSuchConfigFile(..) => None,
|
||||
ConfigError::ParseError(..) => None,
|
||||
ConfigError::BadField(..) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfigFile {
|
||||
/// The parsed configuration for the signer
|
||||
pub struct Config {
|
||||
/// endpoint to the stacks node
|
||||
pub node_host: SocketAddr,
|
||||
/// smart contract that controls the target stackerdb
|
||||
pub stackerdb_contract_id: QualifiedContractIdentifier,
|
||||
/// the private key used to sign blocks, chunks, and transactions
|
||||
pub private_key: StacksPrivateKey,
|
||||
}
|
||||
|
||||
/// Internal struct for loading up the config file
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RawConfigFile {
|
||||
/// endpoint to stacks node
|
||||
pub node_host: String,
|
||||
/// contract identifier
|
||||
pub stackerdb_contract_id: String,
|
||||
/// the private key used to sign blocks, chunks, and transactions in hexademical format
|
||||
pub private_key: String,
|
||||
}
|
||||
|
||||
impl RawConfigFile {
|
||||
/// load the config from a string
|
||||
pub fn load_from_str(data: &str) -> Result<RawConfigFile, ConfigError> {
|
||||
pub fn load_from_str(data: &str) -> Result<Self, ConfigError> {
|
||||
let config: RawConfigFile =
|
||||
toml::from_str(data).map_err(|e| ConfigError::ParseError(format!("{:?}", &e)))?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// load the config from a file
|
||||
pub fn load_from_file(path: &str) -> Result<RawConfigFile, ConfigError> {
|
||||
let data = fs::read_to_string(path)
|
||||
.map_err(|_| ConfigError::NoSuchConfigFile(path.to_string()))?;
|
||||
Self::load_from_str(&data)
|
||||
/// load the config from a file and parse it
|
||||
pub fn load_from_file(path: &str) -> Result<Self, ConfigError> {
|
||||
Self::try_from(&PathBuf::from(path))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawConfigFile> for ConfigFile {
|
||||
impl TryFrom<&PathBuf> for RawConfigFile {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
|
||||
RawConfigFile::load_from_str(&fs::read_to_string(path).map_err(|e| {
|
||||
ConfigError::InvalidConfig(format!("failed to read config file: {:?}", &e))
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawConfigFile> for Config {
|
||||
type Error = ConfigError;
|
||||
|
||||
/// Attempt to decode the raw config file's primitive types into our types.
|
||||
/// NOTE: network access is required for this to work
|
||||
fn try_from(raw_data: RawConfigFile) -> Result<ConfigFile, Self::Error> {
|
||||
fn try_from(raw_data: RawConfigFile) -> Result<Self, Self::Error> {
|
||||
let node_host = raw_data
|
||||
.node_host
|
||||
.clone()
|
||||
@@ -114,23 +109,33 @@ impl TryFrom<RawConfigFile> for ConfigFile {
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(ConfigFile {
|
||||
let private_key = StacksPrivateKey::from_hex(&raw_data.private_key)
|
||||
.map_err(|_| ConfigError::BadField("private_key".to_string(), raw_data.private_key))?;
|
||||
|
||||
Ok(Self {
|
||||
node_host,
|
||||
stackerdb_contract_id,
|
||||
private_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigFile {
|
||||
impl TryFrom<&PathBuf> for Config {
|
||||
type Error = ConfigError;
|
||||
fn try_from(path: &PathBuf) -> Result<Self, ConfigError> {
|
||||
let config_file = RawConfigFile::try_from(path)?;
|
||||
Self::try_from(config_file)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// load the config from a string and parse it
|
||||
pub fn load_from_str(data: &str) -> Result<ConfigFile, ConfigError> {
|
||||
pub fn load_from_str(data: &str) -> Result<Self, ConfigError> {
|
||||
RawConfigFile::load_from_str(data)?.try_into()
|
||||
}
|
||||
|
||||
/// load the config from a file and parse it
|
||||
pub fn load_from_file(path: &str) -> Result<ConfigFile, ConfigError> {
|
||||
let data = fs::read_to_string(path)
|
||||
.map_err(|_| ConfigError::NoSuchConfigFile(path.to_string()))?;
|
||||
Self::load_from_str(&data)
|
||||
pub fn load_from_file(path: &str) -> Result<Self, ConfigError> {
|
||||
Self::try_from(&PathBuf::from(path))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! # stacks-signer: Stacks signer binary for executing DKG rounds, signing transactions and blocks, and more.
|
||||
//!
|
||||
//! Usage documentation can be found in the [README]("https://github.com/blockstack/stacks-blockchain/stacks-signer/README.md).
|
||||
//!
|
||||
//!
|
||||
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
|
||||
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
|
||||
//
|
||||
@@ -13,7 +18,6 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate slog;
|
||||
extern crate stacks_common;
|
||||
|
||||
@@ -24,13 +28,14 @@ extern crate toml;
|
||||
|
||||
mod config;
|
||||
|
||||
use crate::config::Config;
|
||||
use clap::Parser;
|
||||
use libsigner::{SignerSession, StackerDBSession};
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::process;
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use clarity::vm::types::QualifiedContractIdentifier;
|
||||
|
||||
@@ -38,254 +43,151 @@ use stacks_common::types::chainstate::StacksPrivateKey;
|
||||
|
||||
use libstackerdb::StackerDBChunkData;
|
||||
|
||||
/// Consume one argument from `args`, which may go by multiple names in `argnames`.
|
||||
/// If it has an argument (`has_optarg`), then return it.
|
||||
///
|
||||
/// Returns Ok(Some(arg)) if this argument was passed and it has argument `arg`
|
||||
/// Returns Ok(Some("")) if this argument was passed but `has_optarg` is false
|
||||
/// Returns Ok(None) if this argument is not present
|
||||
/// Returns Err(..) if an argument was expected but not found.
|
||||
fn consume_arg(
|
||||
args: &mut Vec<String>,
|
||||
argnames: &[&str],
|
||||
has_optarg: bool,
|
||||
) -> Result<Option<String>, String> {
|
||||
if let Some(ref switch) = args
|
||||
.iter()
|
||||
.find(|ref arg| argnames.iter().find(|ref argname| argname == arg).is_some())
|
||||
{
|
||||
let idx = args
|
||||
.iter()
|
||||
.position(|ref arg| arg == switch)
|
||||
.expect("BUG: did not find the thing that was just found");
|
||||
let argval = if has_optarg {
|
||||
// following argument is the argument value
|
||||
if idx + 1 < args.len() {
|
||||
Some(args[idx + 1].clone())
|
||||
} else {
|
||||
// invalid usage -- expected argument
|
||||
return Err(format!("Expected argument for {}", argnames.join(",")));
|
||||
}
|
||||
} else {
|
||||
// only care about presence of this option
|
||||
Some("".to_string())
|
||||
};
|
||||
|
||||
args.remove(idx);
|
||||
if has_optarg {
|
||||
// also clear the argument
|
||||
args.remove(idx);
|
||||
}
|
||||
Ok(argval)
|
||||
} else {
|
||||
// not found
|
||||
Ok(None)
|
||||
}
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
/// The CLI arguments for the stacks signer
|
||||
pub struct Cli {
|
||||
/// Path to config file
|
||||
#[arg(long, value_name = "FILE")]
|
||||
config: Option<PathBuf>,
|
||||
/// The Stacks node to connect to
|
||||
#[clap(long, required_unless_present = "config", conflicts_with = "config")]
|
||||
host: Option<SocketAddr>,
|
||||
/// The stacker-db contract to use
|
||||
#[arg(short, long, value_parser = parse_contract, required_unless_present = "config", conflicts_with = "config")]
|
||||
contract: Option<QualifiedContractIdentifier>,
|
||||
/// The Stacks private key to use in hexademical format
|
||||
#[arg(short, long, value_parser = parse_private_key, required_unless_present = "config", conflicts_with = "config")]
|
||||
private_key: Option<StacksPrivateKey>,
|
||||
/// Subcommand action to take
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
/// Print an error message, usage, and exit
|
||||
fn usage(err_msg: Option<&str>) {
|
||||
if let Some(err_msg) = err_msg {
|
||||
eprintln!("{}", err_msg);
|
||||
}
|
||||
eprintln!(
|
||||
"Usage: {} subcommand [args]",
|
||||
&env::args().collect::<Vec<_>>()[0]
|
||||
);
|
||||
process::exit(1);
|
||||
/// Subcommands for the stacks signer binary
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
pub enum Command {
|
||||
/// Get a chunk from the stacker-db instance
|
||||
GetChunk(GetChunkArgs),
|
||||
/// Get the latest chunk from the stacker-db instance
|
||||
GetLatestChunk(GetLatestChunkArgs),
|
||||
/// List chunks from the stacker-db instance
|
||||
ListChunks,
|
||||
/// Upload a chunk to the stacker-db instance
|
||||
PutChunk(PutChunkArgs),
|
||||
}
|
||||
|
||||
/// Get -h,--host and -c,--contract
|
||||
fn parse_host_and_contract(argv: &mut Vec<String>) -> (SocketAddr, QualifiedContractIdentifier) {
|
||||
let host_opt = match consume_arg(argv, &["-h", "--host"], true) {
|
||||
Ok(x) => x,
|
||||
Err(msg) => {
|
||||
usage(Some(&msg));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let contract_opt = match consume_arg(argv, &["-c", "--contract"], true) {
|
||||
Ok(x) => x,
|
||||
Err(msg) => {
|
||||
usage(Some(&msg));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let host = match host_opt {
|
||||
Some(host) => match host.to_socket_addrs() {
|
||||
Ok(mut iter) => match iter.next() {
|
||||
Some(host) => host,
|
||||
None => {
|
||||
usage(Some("No hosts resolved"));
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
Err(..) => {
|
||||
usage(Some("Failed to resolve host"));
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
None => {
|
||||
usage(Some("Need -h,--host"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let contract = match contract_opt {
|
||||
Some(host) => match QualifiedContractIdentifier::parse(&host) {
|
||||
Ok(qcid) => qcid,
|
||||
Err(..) => {
|
||||
usage(Some("Invalid contract ID"));
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
None => {
|
||||
usage(Some("Need -c,--contract"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
(host, contract)
|
||||
/// Arguments for the get-chunk command
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct GetChunkArgs {
|
||||
/// The slot ID to get
|
||||
#[arg(long)]
|
||||
slot_id: u32,
|
||||
/// The slot version to get
|
||||
#[arg(long)]
|
||||
slot_version: u32,
|
||||
}
|
||||
|
||||
/// Handle the get-chunk subcommand
|
||||
fn handle_get_chunk(mut argv: Vec<String>) {
|
||||
let (host, contract) = parse_host_and_contract(&mut argv);
|
||||
if argv.len() < 4 {
|
||||
usage(Some("Expected slot_id and slot_version"));
|
||||
}
|
||||
|
||||
let slot_id: u32 = match argv[2].parse() {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Expected u32 for slot ID"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let slot_version: u32 = match argv[3].parse() {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Expected u32 for slot version"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let mut session = StackerDBSession::new(host.clone(), contract.clone());
|
||||
session.connect(host, contract).unwrap();
|
||||
let mut chunk_opt = session.get_chunk(slot_id, slot_version).unwrap();
|
||||
if let Some(chunk) = chunk_opt.take() {
|
||||
io::stdout().write(&chunk).unwrap();
|
||||
}
|
||||
process::exit(0);
|
||||
/// Arguments for the get-latest-chunk command
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct GetLatestChunkArgs {
|
||||
/// The slot ID to get
|
||||
#[arg(long)]
|
||||
slot_id: u32,
|
||||
}
|
||||
|
||||
/// Handle the get-latest-chunk subcommand
|
||||
fn handle_get_latest_chunk(mut argv: Vec<String>) {
|
||||
let (host, contract) = parse_host_and_contract(&mut argv);
|
||||
if argv.len() < 3 {
|
||||
usage(Some("Expected slot_id"));
|
||||
}
|
||||
|
||||
let slot_id: u32 = match argv[2].parse() {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Expected u32 for slot ID"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let mut session = StackerDBSession::new(host.clone(), contract.clone());
|
||||
session.connect(host, contract).unwrap();
|
||||
let chunk_opt = session.get_latest_chunk(slot_id).unwrap();
|
||||
if let Some(chunk) = chunk_opt {
|
||||
io::stdout().write(&chunk).unwrap();
|
||||
}
|
||||
process::exit(0);
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
/// Arguments for the put-chunk command
|
||||
pub struct PutChunkArgs {
|
||||
/// The slot ID to get
|
||||
#[arg(long)]
|
||||
slot_id: u32,
|
||||
/// The slot version to get
|
||||
#[arg(long)]
|
||||
slot_version: u32,
|
||||
/// The data to upload
|
||||
#[arg(required = false, value_parser = parse_data)]
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Handle listing chunks
|
||||
fn handle_list_chunks(mut argv: Vec<String>) {
|
||||
let (host, contract) = parse_host_and_contract(&mut argv);
|
||||
|
||||
let mut session = StackerDBSession::new(host.clone(), contract.clone());
|
||||
session.connect(host, contract).unwrap();
|
||||
let chunk_list = session.list_chunks().unwrap();
|
||||
println!("{}", serde_json::to_string(&chunk_list).unwrap());
|
||||
process::exit(0);
|
||||
/// Parse the contract ID
|
||||
fn parse_contract(contract: &str) -> Result<QualifiedContractIdentifier, String> {
|
||||
QualifiedContractIdentifier::parse(contract).map_err(|e| format!("Invalid contract: {}", e))
|
||||
}
|
||||
|
||||
/// Handle uploading a chunk
|
||||
fn handle_put_chunk(mut argv: Vec<String>) {
|
||||
let (host, contract) = parse_host_and_contract(&mut argv);
|
||||
if argv.len() < 6 {
|
||||
usage(Some("Expected slot_id, slot_version, private_key, data"));
|
||||
}
|
||||
/// Parse the hexadecimal Stacks private key
|
||||
fn parse_private_key(private_key: &str) -> Result<StacksPrivateKey, String> {
|
||||
StacksPrivateKey::from_hex(private_key).map_err(|e| format!("Invalid private key: {}", e))
|
||||
}
|
||||
|
||||
let slot_id: u32 = match argv[2].parse() {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Expected u32 for slot ID"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let slot_version: u32 = match argv[3].parse() {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Expected u32 for slot version"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let privk = match StacksPrivateKey::from_hex(&argv[4]) {
|
||||
Ok(x) => x,
|
||||
Err(..) => {
|
||||
usage(Some("Failed to parse private key"));
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let data = if argv[5] == "-" {
|
||||
/// Parse the input data
|
||||
fn parse_data(data: &str) -> Result<Vec<u8>, String> {
|
||||
let data = if data == "-" {
|
||||
// Parse the data from stdin
|
||||
let mut buf = vec![];
|
||||
io::stdin().read_to_end(&mut buf).unwrap();
|
||||
buf
|
||||
} else {
|
||||
argv[5].as_bytes().to_vec()
|
||||
data.as_bytes().to_vec()
|
||||
};
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Create a new stacker db session
|
||||
fn stackerdb_session(host: SocketAddr, contract: QualifiedContractIdentifier) -> StackerDBSession {
|
||||
let mut session = StackerDBSession::new(host, contract.clone());
|
||||
session.connect(host, contract).unwrap();
|
||||
session
|
||||
}
|
||||
|
||||
/// Write the chunk to stdout
|
||||
fn write_chunk_to_stdout(chunk_opt: Option<Vec<u8>>) {
|
||||
if let Some(chunk) = chunk_opt.as_ref() {
|
||||
let bytes = io::stdout().write(chunk).unwrap();
|
||||
if bytes < chunk.len() {
|
||||
print!(
|
||||
"Failed to write complete chunk to stdout. Missing {} bytes",
|
||||
chunk.len() - bytes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let (host, contract, private_key) = if let Some(config) = cli.config {
|
||||
let config = Config::try_from(&config).unwrap();
|
||||
(
|
||||
config.node_host,
|
||||
config.stackerdb_contract_id,
|
||||
config.private_key,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
cli.host.unwrap(),
|
||||
cli.contract.unwrap(),
|
||||
cli.private_key.unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut chunk = StackerDBChunkData::new(slot_id, slot_version, data);
|
||||
chunk.sign(&privk).unwrap();
|
||||
|
||||
let mut session = StackerDBSession::new(host.clone(), contract.clone());
|
||||
session.connect(host, contract).unwrap();
|
||||
let chunk_ack = session.put_chunk(chunk).unwrap();
|
||||
println!("{}", serde_json::to_string(&chunk_ack).unwrap());
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let argv: Vec<String> = env::args().collect();
|
||||
if argv.len() < 2 {
|
||||
usage(Some("No subcommand given"));
|
||||
}
|
||||
|
||||
let subcommand = argv[1].clone();
|
||||
match subcommand.as_str() {
|
||||
"get-chunk" => {
|
||||
handle_get_chunk(argv);
|
||||
let mut session = stackerdb_session(host, contract);
|
||||
match cli.command {
|
||||
Command::GetChunk(args) => {
|
||||
let chunk_opt = session.get_chunk(args.slot_id, args.slot_version).unwrap();
|
||||
write_chunk_to_stdout(chunk_opt);
|
||||
}
|
||||
"get-latest-chunk" => {
|
||||
handle_get_latest_chunk(argv);
|
||||
Command::GetLatestChunk(args) => {
|
||||
let chunk_opt = session.get_latest_chunk(args.slot_id).unwrap();
|
||||
write_chunk_to_stdout(chunk_opt);
|
||||
}
|
||||
"list-chunks" => {
|
||||
handle_list_chunks(argv);
|
||||
Command::ListChunks => {
|
||||
let chunk_list = session.list_chunks().unwrap();
|
||||
println!("{}", serde_json::to_string(&chunk_list).unwrap());
|
||||
}
|
||||
"put-chunk" => {
|
||||
handle_put_chunk(argv);
|
||||
}
|
||||
_ => {
|
||||
usage(Some(&format!("Unrecognized subcommand '{}'", &subcommand)));
|
||||
Command::PutChunk(args) => {
|
||||
let mut chunk = StackerDBChunkData::new(args.slot_id, args.slot_version, args.data);
|
||||
chunk.sign(&private_key).unwrap();
|
||||
let chunk_ack = session.put_chunk(chunk).unwrap();
|
||||
println!("{}", serde_json::to_string(&chunk_ack).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user