-
Notifications
You must be signed in to change notification settings - Fork 95
feat: Validator database #1614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
feat: Validator database #1614
Changes from 68 commits
9ed9c7a
560c79a
77b1616
0636937
6f4b0d3
71c90b8
7bdd615
4e76c2e
c01448e
2b8008f
4fe9890
f3aaeba
405737e
2d7a3ba
d3c8770
1ec03ef
4735bd1
147b0aa
1596c50
92b16f5
411c8e8
ae5510c
15b9899
bed51da
57b1671
4d1baee
6230ce8
e75e486
2de6f00
189ad4b
62d0cd2
74aa074
2db6985
41e27b5
06bb151
5d76a80
a468514
c0c692b
2e92e72
88624e5
60e1e1f
81db1de
59858c7
37eab16
4fcc6c8
072dbe3
3073aa4
e639c97
cff7f1b
4cb0459
6885616
e612a98
624c581
2289a42
78030a7
425f645
025cc93
6fa8f46
3af9c0a
a6bf55d
a7a2455
e60ab9b
28ca16f
adb60b3
00e45be
5ca7001
0081637
c341458
f3d1cfd
7937a7f
65b5af0
61a9548
d032732
806ec05
959f7e0
528a265
4f46477
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ use miden_node_rpc::Rpc; | |
| use miden_node_store::Store; | ||
| use miden_node_utils::grpc::UrlExt; | ||
| use miden_node_validator::Validator; | ||
| use miden_protocol::block::BlockSigner; | ||
| use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; | ||
| use miden_protocol::utils::Deserializable; | ||
| use tokio::net::TcpListener; | ||
|
|
@@ -22,9 +21,10 @@ use crate::commands::{ | |
| ENV_BLOCK_PROVER_URL, | ||
| ENV_ENABLE_OTEL, | ||
| ENV_GENESIS_CONFIG_FILE, | ||
| ENV_VALIDATOR_INSECURE_SECRET_KEY, | ||
| ENV_VALIDATOR_KEY, | ||
| INSECURE_VALIDATOR_KEY_HEX, | ||
| NtxBuilderConfig, | ||
| ValidatorConfig, | ||
| duration_to_human_readable_string, | ||
| }; | ||
|
|
||
|
|
@@ -51,12 +51,12 @@ pub enum BundledCommand { | |
| /// | ||
| /// If not provided, a predefined key is used. | ||
| #[arg( | ||
| long = "validator.insecure.secret-key", | ||
| env = ENV_VALIDATOR_INSECURE_SECRET_KEY, | ||
| value_name = "VALIDATOR_INSECURE_SECRET_KEY", | ||
| long = "validator.key", | ||
| env = ENV_VALIDATOR_KEY, | ||
| value_name = "VALIDATOR_KEY", | ||
| default_value = INSECURE_VALIDATOR_KEY_HEX | ||
| )] | ||
| validator_insecure_secret_key: String, | ||
| validator_key: String, | ||
| }, | ||
|
|
||
| /// Runs all three node components in the same process. | ||
|
|
@@ -82,6 +82,9 @@ pub enum BundledCommand { | |
| #[command(flatten)] | ||
| ntx_builder: NtxBuilderConfig, | ||
|
|
||
| #[command(flatten)] | ||
| validator: ValidatorConfig, | ||
|
|
||
| /// Enables the exporting of traces for OpenTelemetry. | ||
| /// | ||
| /// This can be further configured using environment variables as defined in the official | ||
|
|
@@ -99,15 +102,6 @@ pub enum BundledCommand { | |
| value_name = "DURATION" | ||
| )] | ||
| grpc_timeout: Duration, | ||
|
|
||
| /// Insecure, hex-encoded validator secret key for development and testing purposes. | ||
| #[arg( | ||
| long = "validator.insecure.secret-key", | ||
| env = ENV_VALIDATOR_INSECURE_SECRET_KEY, | ||
| value_name = "VALIDATOR_INSECURE_SECRET_KEY", | ||
| default_value = INSECURE_VALIDATOR_KEY_HEX | ||
| )] | ||
| validator_insecure_secret_key: String, | ||
| }, | ||
| } | ||
|
|
||
|
|
@@ -118,14 +112,14 @@ impl BundledCommand { | |
| data_directory, | ||
| accounts_directory, | ||
| genesis_config_file, | ||
| validator_insecure_secret_key, | ||
| validator_key, | ||
| } => { | ||
| // Currently the bundled bootstrap is identical to the store's bootstrap. | ||
| crate::commands::store::StoreCommand::Bootstrap { | ||
| data_directory, | ||
| accounts_directory, | ||
| genesis_config_file, | ||
| validator_insecure_secret_key, | ||
| validator_key, | ||
| } | ||
| .handle() | ||
| .await | ||
|
|
@@ -137,20 +131,18 @@ impl BundledCommand { | |
| data_directory, | ||
| block_producer, | ||
| ntx_builder, | ||
| validator, | ||
| enable_otel: _, | ||
| grpc_timeout, | ||
| validator_insecure_secret_key, | ||
| } => { | ||
| let secret_key_bytes = hex::decode(validator_insecure_secret_key)?; | ||
| let signer = SecretKey::read_from_bytes(&secret_key_bytes)?; | ||
| Self::start( | ||
| rpc_url, | ||
| block_prover_url, | ||
| data_directory, | ||
| ntx_builder, | ||
| block_producer, | ||
| ntx_builder, | ||
| validator, | ||
| grpc_timeout, | ||
| signer, | ||
| ) | ||
| .await | ||
| }, | ||
|
|
@@ -162,10 +154,10 @@ impl BundledCommand { | |
| rpc_url: Url, | ||
| block_prover_url: Option<Url>, | ||
| data_directory: PathBuf, | ||
| ntx_builder: NtxBuilderConfig, | ||
| block_producer: BlockProducerConfig, | ||
| ntx_builder: NtxBuilderConfig, | ||
| validator: ValidatorConfig, | ||
| grpc_timeout: Duration, | ||
| signer: impl BlockSigner + Send + Sync + 'static, | ||
| ) -> anyhow::Result<()> { | ||
| // Start listening on all gRPC urls so that inter-component connections can be created | ||
| // before each component is fully started up. | ||
|
|
@@ -177,17 +169,19 @@ impl BundledCommand { | |
| .await | ||
| .context("Failed to bind to RPC gRPC endpoint")?; | ||
|
|
||
| let block_producer_address = TcpListener::bind("127.0.0.1:0") | ||
| .await | ||
| .context("Failed to bind to block-producer gRPC endpoint")? | ||
| .local_addr() | ||
| .context("Failed to retrieve the block-producer's gRPC address")?; | ||
| let (block_producer_url, block_producer_address) = { | ||
| let socket_addr = TcpListener::bind("127.0.0.1:0") | ||
| .await | ||
| .context("Failed to bind to block-producer gRPC endpoint")? | ||
| .local_addr() | ||
| .context("Failed to retrieve the block-producer's gRPC address")?; | ||
| let url = Url::parse(&format!("http://{socket_addr}")) | ||
| .context("Failed to parse Block Producer URL")?; | ||
| (url, socket_addr) | ||
| }; | ||
|
|
||
| let validator_address = TcpListener::bind("127.0.0.1:0") | ||
| .await | ||
| .context("Failed to bind to validator gRPC endpoint")? | ||
| .local_addr() | ||
| .context("Failed to retrieve the validator's gRPC address")?; | ||
| // Validator URL is either specified remote, or generated local. | ||
| let (validator_url, validator_socket_address) = validator.to_addresses().await?; | ||
|
|
||
| // Store addresses for each exposed API | ||
| let store_rpc_listener = TcpListener::bind("127.0.0.1:0") | ||
|
|
@@ -231,85 +225,68 @@ impl BundledCommand { | |
| let should_start_ntx_builder = !ntx_builder.disabled; | ||
|
|
||
| // Start block-producer. The block-producer's endpoint is available after loading completes. | ||
| let block_producer_id = join_set | ||
| .spawn({ | ||
| let store_url = Url::parse(&format!("http://{store_block_producer_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let validator_url = Url::parse(&format!("http://{validator_address}")) | ||
| .context("Failed to parse URL")?; | ||
| async move { | ||
| BlockProducer { | ||
| block_producer_address, | ||
| store_url, | ||
| validator_url, | ||
| batch_prover_url: block_producer.batch_prover_url, | ||
| batch_interval: block_producer.batch_interval, | ||
| block_interval: block_producer.block_interval, | ||
| max_batches_per_block: block_producer.max_batches_per_block, | ||
| max_txs_per_batch: block_producer.max_txs_per_batch, | ||
| grpc_timeout, | ||
| mempool_tx_capacity: block_producer.mempool_tx_capacity, | ||
| let block_producer_id = { | ||
| let validator_url = validator_url.clone(); | ||
| join_set | ||
| .spawn({ | ||
| let store_url = Url::parse(&format!("http://{store_block_producer_address}")) | ||
| .context("Failed to parse URL")?; | ||
| async move { | ||
| BlockProducer { | ||
| block_producer_address, | ||
| store_url, | ||
| validator_url, | ||
| batch_prover_url: block_producer.batch_prover_url, | ||
| batch_interval: block_producer.batch_interval, | ||
| block_interval: block_producer.block_interval, | ||
| max_batches_per_block: block_producer.max_batches_per_block, | ||
| max_txs_per_batch: block_producer.max_txs_per_batch, | ||
| grpc_timeout, | ||
| mempool_tx_capacity: block_producer.mempool_tx_capacity, | ||
| } | ||
| .serve() | ||
| .await | ||
| .context("failed while serving block-producer component") | ||
| } | ||
| .serve() | ||
| .await | ||
| .context("failed while serving block-producer component") | ||
| } | ||
| }) | ||
| .id(); | ||
| }) | ||
| .id() | ||
| }; | ||
|
|
||
| let validator_id = join_set | ||
| .spawn({ | ||
| async move { | ||
| Validator { | ||
| address: validator_address, | ||
| // Start RPC component. | ||
| let rpc_id = { | ||
| let block_producer_url = block_producer_url.clone(); | ||
| let validator_url = validator_url.clone(); | ||
| join_set | ||
| .spawn(async move { | ||
| let store_url = Url::parse(&format!("http://{store_rpc_address}")) | ||
| .context("Failed to parse URL")?; | ||
| Rpc { | ||
| listener: grpc_rpc, | ||
| store_url, | ||
| block_producer_url: Some(block_producer_url), | ||
| validator_url, | ||
| grpc_timeout, | ||
| signer, | ||
| } | ||
| .serve() | ||
| .await | ||
| .context("failed while serving validator component") | ||
| } | ||
| }) | ||
| .id(); | ||
|
|
||
| // Start RPC component. | ||
| let rpc_id = join_set | ||
| .spawn(async move { | ||
| let store_url = Url::parse(&format!("http://{store_rpc_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let block_producer_url = Url::parse(&format!("http://{block_producer_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let validator_url = Url::parse(&format!("http://{validator_address}")) | ||
| .context("Failed to parse URL")?; | ||
| Rpc { | ||
| listener: grpc_rpc, | ||
| store_url, | ||
| block_producer_url: Some(block_producer_url), | ||
| validator_url, | ||
| grpc_timeout, | ||
| } | ||
| .serve() | ||
| .await | ||
| .context("failed while serving RPC component") | ||
| }) | ||
| .id(); | ||
| .context("failed while serving RPC component") | ||
| }) | ||
| .id() | ||
| }; | ||
|
|
||
| // Lookup table so we can identify the failed component. | ||
| let mut component_ids = HashMap::from([ | ||
| (store_id, "store"), | ||
| (block_producer_id, "block-producer"), | ||
| (validator_id, "validator"), | ||
| (rpc_id, "rpc"), | ||
| ]); | ||
|
|
||
| // Start network transaction builder. The endpoint is available after loading completes. | ||
| if should_start_ntx_builder { | ||
| let store_ntx_builder_url = Url::parse(&format!("http://{store_ntx_builder_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let validator_url = Url::parse(&format!("http://{validator_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let block_producer_url = Url::parse(&format!("http://{block_producer_address}")) | ||
| .context("Failed to parse URL")?; | ||
| let block_producer_url = block_producer_url.clone(); | ||
| let validator_url = validator_url.clone(); | ||
|
|
||
| let builder_config = ntx_builder.into_builder_config( | ||
| store_ntx_builder_url, | ||
|
|
@@ -331,6 +308,28 @@ impl BundledCommand { | |
| component_ids.insert(id, "ntx-builder"); | ||
| } | ||
|
|
||
| // Start the Validator if we have bound a socket. | ||
| if let Some(address) = validator_socket_address { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The configuration decision based on the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure that part of the stack should be considering the key. Key presence is considered elsewhere. This part of the stack just cares about whether to run the local instance or not |
||
| let secret_key_bytes = hex::decode(validator.validator_key)?; | ||
| let signer = SecretKey::read_from_bytes(&secret_key_bytes)?; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think its time we clean up our configuration. imo this check/conversion should be done before we actually start any tasks etc. As in,
This is a larger refactor though, so for this PR we can just move on. Something else to note is that |
||
| let id = join_set | ||
| .spawn({ | ||
| async move { | ||
| Validator { | ||
| address, | ||
| grpc_timeout, | ||
| signer, | ||
| data_directory, | ||
| } | ||
| .serve() | ||
| .await | ||
| .context("failed while serving validator component") | ||
| } | ||
| }) | ||
| .id(); | ||
| component_ids.insert(id, "validator"); | ||
| } | ||
|
|
||
| // SAFETY: The joinset is definitely not empty. | ||
| let component_result = join_set.join_next_with_id().await.unwrap(); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.