-
Notifications
You must be signed in to change notification settings - Fork 103
Expand file tree
/
Copy pathvalidator.rs
More file actions
239 lines (217 loc) · 8.34 KB
/
validator.rs
File metadata and controls
239 lines (217 loc) · 8.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use anyhow::Context;
use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig};
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::grpc::UrlExt;
use miden_node_utils::signer::BlockSigner;
use miden_node_validator::{Validator, ValidatorSigner};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
use miden_protocol::utils::serde::{Deserializable, Serializable};
use url::Url;
use crate::commands::{
ENV_DATA_DIRECTORY,
ENV_ENABLE_OTEL,
ENV_GENESIS_CONFIG_FILE,
ENV_VALIDATOR_KEY,
ENV_VALIDATOR_KMS_KEY_ID,
ENV_VALIDATOR_URL,
INSECURE_VALIDATOR_KEY_HEX,
ValidatorKey,
};
/// The filename used for the genesis block file.
pub const GENESIS_BLOCK_FILENAME: &str = "genesis.dat";
#[derive(clap::Subcommand)]
pub enum ValidatorCommand {
/// Bootstraps the genesis block.
///
/// Creates accounts from the genesis configuration, builds and signs the genesis block,
/// and writes the signed block and account secret files to disk.
Bootstrap {
/// Directory in which to write the genesis block file.
#[arg(long, value_name = "DIR")]
genesis_block_directory: PathBuf,
/// Directory to write the account secret files (.mac) to.
#[arg(long, value_name = "DIR")]
accounts_directory: PathBuf,
/// Use the given configuration file to construct the genesis state from.
#[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "GENESIS_CONFIG")]
genesis_config_file: Option<PathBuf>,
/// Configuration for the Validator key used to sign the genesis block.
#[command(flatten)]
validator_key: ValidatorKey,
},
/// Starts the validator component.
Start {
/// Url at which to serve the gRPC API.
#[arg(env = ENV_VALIDATOR_URL)]
url: Url,
/// Enables the exporting of traces for OpenTelemetry.
///
/// This can be further configured using environment variables as defined in the official
/// OpenTelemetry documentation. See our operator manual for further details.
#[arg(long = "enable-otel", default_value_t = true, env = ENV_ENABLE_OTEL, value_name = "BOOL")]
enable_otel: bool,
#[command(flatten)]
grpc_options: GrpcOptionsInternal,
/// Directory in which to store the validator's data.
#[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")]
data_directory: PathBuf,
/// Insecure, hex-encoded validator secret key for development and testing purposes.
///
/// If not provided, a predefined key is used.
///
/// Cannot be used with `key.kms-id`.
#[arg(
long = "key.hex",
env = ENV_VALIDATOR_KEY,
value_name = "VALIDATOR_KEY",
default_value = INSECURE_VALIDATOR_KEY_HEX,
group = "key"
)]
validator_key: String,
/// Key ID for the KMS key used by validator to sign blocks.
///
/// Cannot be used with `key.hex`.
#[arg(
long = "key.kms-id",
env = ENV_VALIDATOR_KMS_KEY_ID,
value_name = "VALIDATOR_KMS_KEY_ID",
group = "key"
)]
kms_key_id: Option<String>,
},
}
impl ValidatorCommand {
/// Runs the validator command.
pub async fn handle(self) -> anyhow::Result<()> {
match self {
Self::Bootstrap {
genesis_block_directory,
accounts_directory,
genesis_config_file,
validator_key,
} => {
Self::bootstrap_genesis(
&genesis_block_directory,
&accounts_directory,
genesis_config_file.as_ref(),
validator_key,
)
.await
},
Self::Start {
url,
grpc_options,
validator_key,
data_directory,
kms_key_id,
..
} => {
let address = url
.to_socket()
.context("failed to extract socket address from validator URL")?;
// Run validator with KMS key backend if key id provided.
if let Some(kms_key_id) = kms_key_id {
let signer = ValidatorSigner::new_kms(kms_key_id).await?;
Self::serve(address, grpc_options, signer, data_directory).await
} else {
let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?;
let signer = ValidatorSigner::new_local(signer);
Self::serve(address, grpc_options, signer, data_directory).await
}
},
}
}
/// Runs the validator component until failure.
async fn serve(
address: SocketAddr,
grpc_options: GrpcOptionsInternal,
signer: ValidatorSigner,
data_directory: PathBuf,
) -> anyhow::Result<()> {
Validator {
address,
grpc_options,
signer,
data_directory,
}
.serve()
.await
.context("failed while serving validator component")
}
pub fn is_open_telemetry_enabled(&self) -> bool {
match self {
Self::Start { enable_otel, .. } => *enable_otel,
Self::Bootstrap { .. } => false,
}
}
/// Bootstraps the genesis block: creates accounts, signs the block, and writes artifacts to
/// disk.
///
/// This is extracted as a free function so it can be reused by the bundled bootstrap command.
pub async fn bootstrap_genesis(
genesis_block_directory: &Path,
accounts_directory: &Path,
genesis_config: Option<&PathBuf>,
validator_key: ValidatorKey,
) -> anyhow::Result<()> {
// Parse genesis config (or default if not given).
let config = genesis_config
.map(|file_path| {
GenesisConfig::read_toml_file(file_path).with_context(|| {
format!("failed to parse genesis config from file {}", file_path.display())
})
})
.transpose()?
.unwrap_or_default();
// Create directories if they do not already exist.
for directory in [accounts_directory, genesis_block_directory] {
ensure_empty_directory(directory)?;
}
// Bootstrap with KMS key or local key.
let signer = validator_key.into_signer().await?;
match signer {
ValidatorSigner::Kms(signer) => {
build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory)
.await
},
ValidatorSigner::Local(signer) => {
build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory)
.await
},
}
}
}
/// Builds the genesis state, writes account secret files, signs the genesis block, and writes it
/// to disk.
async fn build_and_write_genesis(
config: GenesisConfig,
signer: impl BlockSigner,
accounts_directory: &Path,
genesis_block_directory: &Path,
) -> anyhow::Result<()> {
// Build genesis state with the provided signer.
let (genesis_state, secrets) = config.into_state(signer)?;
// Write account secret files.
for item in secrets.as_account_files(&genesis_state) {
let AccountFileWithName { account_file, name } = item?;
let accountpath = accounts_directory.join(name);
// Do not override existing keys.
fs_err::OpenOptions::new()
.create_new(true)
.write(true)
.open(&accountpath)
.context("key file already exists")?;
account_file.write(accountpath)?;
}
// Build the signed genesis block.
let genesis_block =
genesis_state.into_block().await.context("failed to build the genesis block")?;
// Serialize and write the genesis block to disk.
let block_bytes = genesis_block.inner().to_bytes();
let genesis_block_path = genesis_block_directory.join(GENESIS_BLOCK_FILENAME);
fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?;
Ok(())
}