From 6939c5f73b497aacc892e9d70be1efe572c8b6d2 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Tue, 2 Dec 2025 13:46:25 -0800 Subject: [PATCH 01/13] feat(cli): rework node configuration directory --- Cargo.lock | 128 ++++++++++---------- README.md | 158 +++++++++++++++++++++---- cli/src/commands/clean.rs | 51 +++++--- cli/src/commands/start.rs | 114 +++++++++++++++--- cli/src/helpers/args.rs | 35 +++++- node/bft/examples/simple_node.rs | 10 +- node/bft/src/bft.rs | 13 +- node/bft/src/gateway.rs | 29 +++-- node/bft/src/helpers/proposal_cache.rs | 36 ++---- node/bft/src/primary.rs | 22 ++-- node/bft/src/sync/mod.rs | 5 +- node/bft/tests/common/primary.rs | 6 +- node/bft/tests/common/utils.rs | 9 +- node/consensus/Cargo.toml | 3 + node/consensus/src/lib.rs | 4 +- node/network/Cargo.toml | 3 - node/network/src/peering.rs | 15 +-- node/router/Cargo.toml | 6 +- node/router/src/lib.rs | 15 +-- node/router/tests/common/mod.rs | 9 +- node/src/client/mod.rs | 5 +- node/src/node.rs | 10 +- node/src/prover/mod.rs | 8 +- node/src/validator/mod.rs | 8 +- node/tests/common/node.rs | 6 +- utilities/Cargo.toml | 2 +- utilities/src/lib.rs | 3 + utilities/src/node_data.rs | 102 ++++++++++++++++ utilities/src/signals.rs | 3 +- 29 files changed, 579 insertions(+), 239 deletions(-) create mode 100644 utilities/src/node_data.rs diff --git a/Cargo.lock b/Cargo.lock index b97e470ba8..b3cc78165e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core 0.5.5", "bytes", @@ -362,7 +362,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ - "axum 0.8.7", + "axum 0.8.8", "axum-core 0.5.5", "bytes", "futures-util", @@ -416,9 +416,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bech32" @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -927,7 +927,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -1165,18 +1165,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -1683,9 +1683,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "git2" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" dependencies = [ "bitflags 2.10.0", "libc", @@ -2145,9 +2145,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -2159,9 +2159,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -2325,9 +2325,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jobserver" @@ -2396,9 +2396,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -2424,9 +2424,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", @@ -3038,9 +3038,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "potential_utf" @@ -3483,9 +3483,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -3624,9 +3624,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -3652,9 +3652,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -3703,9 +3703,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -3867,9 +3867,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "indexmap 2.12.1", "itoa", @@ -3892,9 +3892,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -4021,9 +4021,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_asn1" @@ -4094,7 +4094,7 @@ dependencies = [ "snarkos-node-tcp", "snarkvm", "tikv-jemallocator", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tracing", "walkdir", ] @@ -4213,7 +4213,7 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", - "axum 0.8.7", + "axum 0.8.8", "axum-extra", "bytes", "clap", @@ -4348,6 +4348,7 @@ dependencies = [ "snarkos-node-bft-storage-service", "snarkos-node-metrics", "snarkos-node-sync", + "snarkos-utilities", "snarkvm", "tokio", "tracing", @@ -4370,7 +4371,6 @@ dependencies = [ name = "snarkos-node-network" version = "4.4.0" dependencies = [ - "aleo-std", "anyhow", "built", "locktick", @@ -4388,7 +4388,7 @@ name = "snarkos-node-rest" version = "4.4.0" dependencies = [ "anyhow", - "axum 0.8.7", + "axum 0.8.8", "axum-extra", "base64 0.22.1", "built", @@ -4421,7 +4421,6 @@ dependencies = [ name = "snarkos-node-router" version = "4.4.0" dependencies = [ - "aleo-std", "anyhow", "async-trait", "colored 3.0.0", @@ -4444,6 +4443,7 @@ dependencies = [ "snarkos-node-sync-communication-service", "snarkos-node-sync-locators", "snarkos-node-tcp", + "snarkos-utilities", "snarkvm", "time", "tokio", @@ -5689,7 +5689,7 @@ dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -5986,9 +5986,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap 2.12.1", "serde_core", @@ -6001,27 +6001,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -6091,9 +6091,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -6135,7 +6135,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e6672c7510df74859726427edea641674dad1aeeb30057b87335b1ba23b843" dependencies = [ - "axum 0.8.7", + "axum 0.8.8", "forwarded-header-value", "governor", "http 1.4.0", @@ -6147,9 +6147,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6170,9 +6170,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6544,7 +6544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.1.2", + "rustix 1.1.3", "winsafe", ] diff --git a/README.md b/README.md index 1a6ed94aaa..e68c06c6eb 100644 --- a/README.md +++ b/README.md @@ -299,30 +299,142 @@ SUBCOMMANDS: The following are the options for the `snarkos start` command: ``` -USAGE: - snarkos start [OPTIONS] + --network + Specify the network ID of this node [options: 0 = mainnet, 1 = testnet, 2 = canary] + + [default: 0] -OPTIONS: - --network Specify the network ID of this node [default: 3] - - --validator Specify this node as a validator - --prover Specify this node as a prover - --client Specify this node as a client - - --private-key Specify the node's account private key - --private-key-file Specify the path to a file containing the node's account private key - - --node Specify the IP address and port for the node server [default: 0.0.0.0:4130] - --connect Specify the IP address and port of a peer to connect to - - --rest Specify the IP address and port for the REST server [default: 0.0.0.0:3030] - --norest If the flag is set, the node will not initialize the REST server - - --nodisplay If the flag is set, the node will not render the display - --verbosity Specify the verbosity of the node [options: 0, 1, 2, 3] [default: 2] - --logfile Specify the path to the file where logs will be stored [default: /tmp/snarkos.log] - - --dev Enables development mode, specify a unique ID for this node + --prover + Start the node as a prover + + --client + Start the node as a client (default). + + Client are "full nodes", i.e, validate and execute all blocks they receive, but they do not participate in AleoBFT consensus. + + --bootstrap-client + Start the node as a bootstrap client. + + --validator + Start the node as a validator. + + Validators are "full nodes", like clients, but also participate in AleoBFT. + + --noupdater + Disable checking for new versions at startup + + --private-key + Specify the account private key of the node + + --private-key-file + Specify the path to a file containing the account private key of the node + + --node + Set the IP address and port used for P2P communication + + --bft + Set the IP address and port used for BFT communication. This argument is only allowed for validator nodes + + --peers + Specify the IP address and port of the peer(s) to connect to (as a comma-separated list). + + These peers will be set as "trusted", which means the node will not disconnect from them when performing peer rotation. + + Setting peers to "" has the same effect as not setting the flag at all, except when using `--dev`. + + --validators + Specify the IP address and port of the validator(s) to connect to + + --rest + Specify the IP address and port for the REST server + + --rest-rps + Specify the requests per second (RPS) rate limit per IP for the REST server + + [default: 10] + + --jwt-secret + Specify the JWT secret for the REST server (16B, base64-encoded) + + --jwt-timestamp + Specify the JWT creation timestamp; can be any time in the last 10 years + + --norest + If the flag is set, the node will not initialize the REST server + + --nojwt + If the flag is set, the node will not require JWT authentication for the REST server + + --trusted-peers-only + If the flag is set, the node will only connect to trusted peers and validators + + --nodisplay + Write log message to stdout instead of showing a terminal UI. + + This is useful, for example, for running a node as a service instead of in the foreground or to pipe its output into a file. + + --verbosity + Specify the log verbosity of the node. [options: 0 (lowest log level) to 6 (highest level)] + + [default: 1] + + --log-filter + Set a custom log filtering scheme, e.g., "off,snarkos_bft=trace", to show all log messages of snarkos_bft but nothing else + + --logfile + Specify the path to the file where logs will be stored + + [default: /var/folders/6v/1bwnpyjd1r5f9wr_9hq25qsm0000gn/T/snarkos.log] + + --metrics + Enable the metrics exporter + + --metrics-ip + Specify the IP address and port for the metrics exporter + + --storage + Specify the path to a directory containing the storage database for the ledger. This flag overrides the default path, even when `--dev` is set + + --node-data + Set a custom path for the node-specific data. + This flag overrides the default path, even when `--dev` is set. + + That folder may contain sensitive data, such as the JWT secret, and should not be shared with untrusted parties. + For validators, it also contains the latest proposal cache, which is required to participate in consensus. + + --cdn + Enables the node to prefetch initial blocks from a CDN + + --nocdn + If the flag is set, the node will not prefetch from a CDN + + --dev + Enables development mode used to set up test networks. + + The purpose of this flag is to run multiple nodes on the same machine and in the same working directory. + To do this, set the value to a unique ID within the test work. For example if there are four nodes in the network, pass `--dev 0` for the first node, `--dev 1` for the second, and so forth. + + If you do not explicitly set the `--peers` flag, this will also populate the set of trusted peers, so that the network is fully connected. + Additionally, if you do not set the `--rest` or the `--norest` flags, it will also set the REST port to `3030` for the first node, `3031` for the second, and so forth. + + --dev-num-validators + If development mode is enabled, specify the number of genesis validator + + [default: 4] + + --dev-num-clients + If development mode is enabled, specify the number of clients. This is only used by validators to automatically populate their set of trusted peers. + + This option cannot be used while also passing the `--peers` flag. + + --no-dev-txs + If development mode is enabled, specify whether node 0 should generate traffic to drive the network + + --dev-bonded-balances + If development mode is enabled, specify the custom bonded balances as a JSON object + + -h, --help + Print help (see a summary with '-h') ``` ## 6. Development Guide diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs index a16db9485f..c24f907e0b 100644 --- a/cli/src/commands/clean.rs +++ b/cli/src/commands/clean.rs @@ -13,11 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use snarkos_node::bft::helpers::proposal_cache_path; +use crate::helpers::args::parse_node_data_dir; + +use snarkos_utilities::NodeDataDir; + use snarkvm::console::network::{CanaryV0, MainnetV0, Network}; use aleo_std::StorageMode; -use anyhow::{Result, bail}; +use anyhow::{Context, Result}; use clap::Parser; use colored::Colorize; use std::path::PathBuf; @@ -35,12 +38,20 @@ pub struct Clean { /// dev). #[clap(long = "path")] pub path: Option, + + /// Sets a custom path for the node configuration. + #[clap(long)] + pub node_config_dir: Option, } impl Clean { /// Cleans the snarkOS node storage. pub fn parse(self) -> Result { - // Initialize the storage mode. + // Remove the specified node configuration from storage. + let node_config_dir = parse_node_data_dir(&self.node_config_dir, self.network, self.dev)?; + println!("{}", Self::remove_node_data(&node_config_dir)?); + + // Remove the specified ledger from storage. let storage_mode = match self.path { Some(path) => StorageMode::Custom(path), None => match self.dev { @@ -48,16 +59,23 @@ impl Clean { None => StorageMode::Production, }, }; + Self::remove_ledger(self.network, &storage_mode) + } + + /// Removes the specified node configuration from storage. + fn remove_node_data(node_data_dir: &NodeDataDir) -> Result { + // With the new layout, we can remove the entire folder. + let data_path = node_data_dir.path(); + + // Prepare the path string. + let path_string = format!("(in \"{}\")", data_path.display()).dimmed(); - // Remove the current proposal cache file, if it exists. - let proposal_cache_path = proposal_cache_path(self.network, &storage_mode); - if proposal_cache_path.exists() { - if let Err(err) = std::fs::remove_file(&proposal_cache_path) { - bail!("Failed to remove the current proposal cache file at {}: {err}", proposal_cache_path.display()); - } + if data_path.exists() { + std::fs::remove_dir_all(data_path).with_context(|| format!("Failed to remove node data {path_string}"))?; + Ok(format!("✅ Cleaned up node data {path_string}")) + } else { + Ok(format!("✅ No node data was found {path_string}")) } - // Remove the specified ledger from storage. - Self::remove_ledger(self.network, &storage_mode) } /// Removes the specified ledger from storage. @@ -71,14 +89,11 @@ impl Clean { // Check if the path to the ledger exists in storage. if path.exists() { // Remove the ledger files from storage. - match std::fs::remove_dir_all(&path) { - Ok(_) => Ok(format!("✅ Cleaned the snarkOS node storage {path_string}")), - Err(error) => { - bail!("Failed to remove the snarkOS node storage {path_string}\n{}", error.to_string().dimmed()) - } - } + std::fs::remove_dir_all(&path) + .with_context(|| format!("Failed to remove the snarkOS ledger {path_string}"))?; + Ok(format!("✅ Cleaned the snarkOS ledger {path_string}")) } else { - Ok(format!("✅ No snarkOS node storage was found {path_string}")) + Ok(format!("✅ No snarkOS ledger was found {path_string}")) } } } diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 6e6a03531e..cd5cfd0e40 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -13,7 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::helpers::{args::network_id_parser, dev::*}; +use crate::helpers::{ + args::{network_id_parser, parse_node_data_dir}, + dev::*, +}; use snarkos_account::Account; use snarkos_display::Display; @@ -24,7 +27,7 @@ use snarkos_node::{ rest::DEFAULT_REST_PORT, router::DEFAULT_NODE_PORT, }; -use snarkos_utilities::SignalHandler; +use snarkos_utilities::{NodeDataDir, SignalHandler, node_data}; use snarkvm::{ console::{ @@ -42,7 +45,7 @@ use snarkvm::{ utilities::to_bytes_le, }; -use aleo_std::StorageMode; +use aleo_std::{StorageMode, aleo_ledger_dir}; use anyhow::{Context, Result, anyhow, bail, ensure}; use base64::prelude::{BASE64_STANDARD, Engine}; use clap::Parser; @@ -52,9 +55,10 @@ use indexmap::IndexMap; use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; use serde::{Deserialize, Serialize}; + use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs}, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, atomic::AtomicBool}, }; use tokio::{ @@ -237,6 +241,14 @@ pub struct Start { #[clap(long)] pub storage: Option, + /// Set a custom path for the node-specific data. + /// This flag overrides the default path, even when `--dev` is set. + /// + /// That folder may contain sensitive data, such as the JWT secret, and should not be shared with untrusted parties. + /// For validators, it also contains the latest proposal cache, which is required to participate in consensus. + #[clap(long, verbatim_doc_comment)] + pub node_data: Option, + /// Enables the node to prefetch initial blocks from a CDN #[clap(long, conflicts_with = "nocdn")] pub cdn: Option, @@ -488,6 +500,13 @@ impl Start { Ok(()) } + /// Returns the path to where the JWT secret for the node is stored. + fn jwt_secret_path(node_data_dir: &NodeDataDir, address: &Address) -> Result { + let mut path = node_data_dir.path().to_path_buf(); + path.push(format!("jwt_secret_{address}.txt")); + Ok(path) + } + /// Returns an alternative genesis block if the node is in development mode. /// Otherwise, returns the actual genesis block. fn parse_genesis(&self) -> Result> { @@ -695,13 +714,31 @@ impl Start { }, }; - // Helper function to store the JWT secret. - let store_jwt_secret = |network: u16, storage_mode: &StorageMode, address: &Address, token: String| -> Result<()> { - let mut jwt_secret_path = aleo_std::aleo_ledger_dir(network, storage_mode); - std::fs::create_dir_all(&jwt_secret_path)?; - jwt_secret_path.push(format!("jwt_secret_{address}.txt")); - Ok(std::fs::write(jwt_secret_path, token)?) - }; + if self.node_data.is_some() && !matches!(storage_mode, StorageMode::Custom(_)) { + warn!("Custom path set for `--storage`, but not for `--node-data`. The latter will use the default path."); + } else if matches!(storage_mode, StorageMode::Custom(_)) && self.node_data.is_none() { + warn!("Custom path set for `--storage`, but `--node-data` is not set. The latter will use the default path."); + } + + // Parse the node data directory. + let node_data_dir = parse_node_data_dir(&self.node_data, N::ID, self.dev).with_context(|| "Failed to setup node configuration directory")?; + + // Make sure the directory exists before continue. + let data_path = node_data_dir.path(); + if !data_path.exists() { + info!("Creating node data directory at {data_path:?}"); + std::fs::create_dir_all(data_path) + .with_context(|| format!("Failed to create node data directory at {data_path:?}"))? + } else if !data_path.is_dir() { + bail!("Node data directory at {data_path:?} is not a directory"); + } else { + debug!("Using existing node data directory at {data_path:?}"); + } + + // Checks for the old storage format and prints instructions to migrate. + // We perform this check after creating the node data directory, so that migrating the data is easier. + Self::check_for_old_storage_format(&aleo_ledger_dir(N::ID, &storage_mode), &account.address(), &node_data_dir, self.dev).with_context(|| "Node still uses the old storage format")?; + // Compute the optional REST server JWT. let jwt_token = if self.nojwt { None @@ -714,14 +751,16 @@ impl Start { // Create the JWT token based on the given secret. let jwt_token = snarkos_node_rest::Claims::new(account.address(), Some(jwt_bytes), self.jwt_timestamp).to_jwt_string()?; // Store the JWT secret to a file. - store_jwt_secret(self.network, &storage_mode, &account.address(), jwt_token.clone())?; + let path = Self::jwt_secret_path(&node_data_dir, &account.address())?; + std::fs::write(path, &jwt_token)?; // Return the JWT token for optional printing. Some(jwt_token) } else { // Create a random JWT token. let jwt_token = snarkos_node_rest::Claims::new(account.address(), None, self.jwt_timestamp).to_jwt_string()?; // Store the JWT secret to a file. - store_jwt_secret(self.network, &storage_mode, &account.address(), jwt_token.clone())?; + let path = Self::jwt_secret_path(&node_data_dir, &account.address())?; + std::fs::write(path, &jwt_token)? ; // Return the JWT token for optional printing. Some(jwt_token) }; @@ -781,9 +820,9 @@ impl Start { // Initialize the node. let node = match node_type { - NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, self.trusted_peers_only, dev_txs, self.dev, signal_handler.clone()).await, - NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, storage_mode, self.trusted_peers_only, self.dev, signal_handler.clone()).await, - NodeType::Client => Node::new_client(node_ip, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode, self.trusted_peers_only, self.dev, signal_handler.clone()).await, + NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, dev_txs, self.dev, signal_handler.clone()).await, + NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, node_data_dir, self.trusted_peers_only, self.dev, signal_handler.clone()).await, + NodeType::Client => Node::new_client(node_ip, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, self.dev, signal_handler.clone()).await, NodeType::BootstrapClient => Node::new_bootstrap_client(node_ip, account, *genesis.header(), self.dev).await, }?; @@ -795,6 +834,49 @@ impl Start { Ok(()) } + /// Check if the node is still using the old storage format, + /// in which case we print an error and exit. + /// We detect this by checking if + /// - a peer-cache file exists inside the ledger directory, + /// - a current-proposal-cache file exists at the parent directory of the ledger directory + /// - a jwt_secret_*.txt file exists at the parent directory of the ledger directory + fn check_for_old_storage_format( + ledger_dir: &Path, + address: &Address, + node_data_dir: &NodeDataDir, + dev: Option, + ) -> Result<()> { + let ledger_parent_dir = ledger_dir.parent().unwrap(); + + // Determine the old paths used for node configuration files. + let old_router_cache_path = ledger_dir.join(node_data::LEGACY_ROUTER_PEER_CACHE_FILE); + let old_gatway_cache_path = ledger_dir.join(node_data::LEGACY_GATWAY_PEER_CACHE_FILE); + let old_proposal_cache_path = ledger_dir.join(node_data::legacy_current_proposal_cache_file(N::ID, dev)); + let old_jwt_secret_path = ledger_parent_dir.join(node_data::jwt_secret_file(address)); + + if old_router_cache_path.exists() { + let new_router_cache_path = node_data_dir.router_peer_cache_path(); + bail!("Please migrate {old_router_cache_path:?} to {new_router_cache_path:?} before restarting."); + } + + if old_gatway_cache_path.exists() { + let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); + bail!("Please migrate {old_gatway_cache_path:?} to {new_gateway_cache_path:?} before restarting."); + } + + if old_proposal_cache_path.exists() { + let new_proposal_cache_path = node_data_dir.current_proposal_cache_path(); + bail!("Please migrate {old_proposal_cache_path:?} to {new_proposal_cache_path:?} before restarting."); + } + + if old_jwt_secret_path.exists() { + let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address); + bail!("Please migrate {old_jwt_secret_path:?} to {new_jwt_secret_path:?} before restarting."); + } + + Ok(()) + } + /// Starts a rayon thread pool and tokio runtime for the node, and returns the tokio `Runtime`. fn runtime() -> Runtime { // Retrieve the number of cores. diff --git a/cli/src/helpers/args.rs b/cli/src/helpers/args.rs index 939c33ca73..f7956a9639 100644 --- a/cli/src/helpers/args.rs +++ b/cli/src/helpers/args.rs @@ -13,18 +13,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::helpers::dev::get_development_key; + +use snarkos_utilities::NodeDataDir; + use snarkvm::{ console::network::{CanaryV0, MainnetV0}, prelude::{Network, PrivateKey}, }; +use aleo_std::aleo_dir; use anyhow::{Context, Result, anyhow}; use clap::builder::RangedU64ValueParser; use std::{path::PathBuf, str::FromStr}; use ureq::http::{Uri, uri}; -use crate::helpers::dev::get_development_key; - pub(crate) fn network_id_parser() -> RangedU64ValueParser { RangedU64ValueParser::::new().range((MainnetV0::ID as u64)..=(CanaryV0::ID as u64)) } @@ -74,6 +77,34 @@ pub(crate) fn parse_private_key( Ok(private_key) } +/// Returns the path to the node configuration directory. +pub(crate) fn parse_node_data_dir( + node_config_dir: &Option, + network: u16, + dev: Option, +) -> Result { + if let Some(custom) = node_config_dir { + return Ok(NodeDataDir::new(custom.clone())); + } + + let config = if let Some(dev) = dev { + // Development mode + let mut path = match std::env::current_dir() { + Ok(current_dir) => current_dir, + _ => PathBuf::from(env!("CARGO_MANIFEST_DIR")), + }; + + path.push(format!(".node-data-{network}-{dev}")); + NodeDataDir::new(path) + } else { + // Production mode + let path = aleo_dir().join("storage").join(format!("node-data-{network}")); + NodeDataDir::new(path) + }; + + Ok(config) +} + #[cfg(test)] mod tests { use super::*; diff --git a/node/bft/examples/simple_node.rs b/node/bft/examples/simple_node.rs index 99536287f1..a7ad7d710d 100644 --- a/node/bft/examples/simple_node.rs +++ b/node/bft/examples/simple_node.rs @@ -29,7 +29,7 @@ use snarkos_node_bft::{ use snarkos_node_bft_ledger_service::TranslucentLedgerService; use snarkos_node_bft_storage_service::BFTMemoryService; use snarkos_node_sync::BlockSync; -use snarkos_utilities::SimpleStoppable; +use snarkos_utilities::{NodeDataDir, SimpleStoppable}; use aleo_std::StorageMode; use snarkvm::{ @@ -140,7 +140,7 @@ pub async fn start_bft( Some(ip) => Some(*ip), None => Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + node_id)), }; - let storage_mode = StorageMode::new_test(None); + let node_config_dir = NodeDataDir::new_test(None); // Initialize the trusted validators. let trusted_validators = trusted_validators(node_id, num_nodes, peers); let trusted_peers_only = false; @@ -158,7 +158,7 @@ pub async fn start_bft( ip, &trusted_validators, trusted_peers_only, - storage_mode, + node_config_dir, None, )?; // Run the BFT instance. @@ -194,7 +194,7 @@ pub async fn start_primary( Some(ip) => Some(*ip), None => Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + node_id)), }; - let storage_mode = StorageMode::new_test(None); + let node_config_dir = NodeDataDir::new_test(None); // Initialize the trusted validators. let trusted_validators = trusted_validators(node_id, num_nodes, peers); let trusted_peers_only = false; @@ -208,7 +208,7 @@ pub async fn start_primary( ip, &trusted_validators, trusted_peers_only, - storage_mode, + node_config_dir, None, )?; // Run the primary instance. diff --git a/node/bft/src/bft.rs b/node/bft/src/bft.rs index 0ce559dab7..a8f7cd5326 100644 --- a/node/bft/src/bft.rs +++ b/node/bft/src/bft.rs @@ -28,9 +28,12 @@ use crate::{ now, }, }; + use snarkos_account::Account; use snarkos_node_bft_ledger_service::LedgerService; use snarkos_node_sync::{BlockSync, Ping}; +use snarkos_utilities::NodeDataDir; + use snarkvm::{ console::account::Address, ledger::{ @@ -43,7 +46,6 @@ use snarkvm::{ utilities::flatten_error, }; -use aleo_std::StorageMode; use anyhow::Context; use colored::Colorize; use indexmap::{IndexMap, IndexSet}; @@ -99,7 +101,7 @@ impl BFT { ip: Option, trusted_validators: &[SocketAddr], trusted_peers_only: bool, - storage_mode: StorageMode, + node_config_dir: NodeDataDir, dev: Option, ) -> Result { Ok(Self { @@ -111,7 +113,7 @@ impl BFT { ip, trusted_validators, trusted_peers_only, - storage_mode, + node_config_dir, dev, )?, dag: Default::default(), @@ -994,6 +996,8 @@ mod tests { use snarkos_node_bft_ledger_service::{LedgerService, MockLedgerService}; use snarkos_node_bft_storage_service::BFTMemoryService; use snarkos_node_sync::BlockSync; + use snarkos_utilities::NodeDataDir; + use snarkvm::{ console::account::{Address, PrivateKey}, ledger::{ @@ -1013,7 +1017,6 @@ mod tests { utilities::TestRng, }; - use aleo_std::StorageMode; use anyhow::Result; use indexmap::{IndexMap, IndexSet}; use std::sync::Arc; @@ -1060,7 +1063,7 @@ mod tests { None, &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), None, ) } diff --git a/node/bft/src/gateway.rs b/node/bft/src/gateway.rs index 6e5ac4319a..f24ad0d418 100644 --- a/node/bft/src/gateway.rs +++ b/node/bft/src/gateway.rs @@ -24,7 +24,6 @@ use crate::{ helpers::{Cache, PrimarySender, Storage, SyncSender, WorkerSender, assign_to_worker}, spawn_blocking, }; -use aleo_std::StorageMode; use snarkos_account::Account; use snarkos_node_bft_events::{ BlockRequest, @@ -61,6 +60,7 @@ use snarkos_node_tcp::{ Tcp, protocols::{Disconnect, Handshake, OnConnect, Reading, Writing}, }; +use snarkos_utilities::NodeDataDir; use snarkvm::{ console::prelude::*, ledger::{ @@ -116,9 +116,6 @@ const CONNECTION_ATTEMPTS_SINCE_SECS: i64 = 10; /// The amount of time an IP address is prohibited from connecting. const IP_BAN_TIME_IN_SECS: u64 = 300; -/// The name of the file containing cached validators. -const VALIDATOR_CACHE_FILENAME: &str = "cached_gateway_peers"; - /// Part of the Gateway API that deals with networking. /// This is a separate trait to allow for easier testing/mocking. #[async_trait] @@ -166,7 +163,7 @@ pub struct InnerGateway { /// The spawned handles. handles: Mutex>>, /// The storage mode. - storage_mode: StorageMode, + node_data_dir: NodeDataDir, /// If the flag is set, the node will only connect to trusted peers. trusted_peers_only: bool, /// The development mode. @@ -209,7 +206,7 @@ impl Gateway { ip: Option, trusted_validators: &[SocketAddr], trusted_peers_only: bool, - storage_mode: StorageMode, + node_data_dir: NodeDataDir, dev: Option, ) -> Result { // Initialize the gateway IP. @@ -226,7 +223,7 @@ impl Gateway { // Load entries from the validator cache (if present and if we are not in trusted peers only mode). if !trusted_peers_only { - let cached_peers = Self::load_cached_peers(&storage_mode, VALIDATOR_CACHE_FILENAME)?; + let cached_peers = Self::load_cached_peers(&node_data_dir.gateway_peer_cache_path())?; for addr in cached_peers { initial_peers.insert(addr, Peer::new_candidate(addr, false)); } @@ -251,7 +248,7 @@ impl Gateway { worker_senders: Default::default(), sync_sender: Default::default(), handles: Default::default(), - storage_mode, + node_data_dir, trusted_peers_only, dev, }))) @@ -845,7 +842,7 @@ impl Gateway { pub async fn shut_down(&self) { info!("Shutting down the gateway..."); // Save the best peers for future use. - if let Err(e) = self.save_best_peers(&self.storage_mode, VALIDATOR_CACHE_FILENAME, None) { + if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None) { warn!("Failed to persist best validators to disk: {e}"); } // Abort the tasks. @@ -1669,12 +1666,14 @@ mod prop_tests { gateway::prop_tests::GatewayAddress::{Dev, Prod}, helpers::{Storage, init_primary_channels, init_worker_channels}, }; - use aleo_std::StorageMode; + use snarkos_account::Account; use snarkos_node_bft_ledger_service::MockLedgerService; use snarkos_node_bft_storage_service::BFTMemoryService; use snarkos_node_network::PeerPoolHandling; use snarkos_node_tcp::P2P; + use snarkos_utilities::NodeDataDir; + use snarkvm::{ ledger::{ committee::{ @@ -1745,7 +1744,7 @@ mod prop_tests { address.ip(), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), address.port(), ) .unwrap() @@ -1798,7 +1797,7 @@ mod prop_tests { dev.ip(), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), dev.port(), ) .unwrap(); @@ -1823,7 +1822,7 @@ mod prop_tests { dev.ip(), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), dev.port(), ) .unwrap(); @@ -1858,7 +1857,7 @@ mod prop_tests { dev.ip(), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), dev.port(), ) .unwrap(); @@ -1932,7 +1931,7 @@ mod prop_tests { dev.ip(), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), dev.port(), ) .unwrap(); diff --git a/node/bft/src/helpers/proposal_cache.rs b/node/bft/src/helpers/proposal_cache.rs index 6e19f28285..419d37fc2f 100644 --- a/node/bft/src/helpers/proposal_cache.rs +++ b/node/bft/src/helpers/proposal_cache.rs @@ -14,6 +14,7 @@ // limitations under the License. use crate::helpers::{Proposal, SignedProposals}; +use snarkos_utilities::NodeDataDir; use snarkvm::{ console::{account::Address, network::Network, program::SUBDAG_CERTIFICATES_DEPTH}, @@ -21,27 +22,12 @@ use snarkvm::{ prelude::{FromBytes, IoResult, Read, Result, ToBytes, Write, anyhow, bail, error}, }; -use aleo_std::{StorageMode, aleo_ledger_dir}; use indexmap::IndexSet; use std::{fs, path::PathBuf}; /// Returns the path where a proposal cache file may be stored. -pub fn proposal_cache_path(network: u16, storage_mode: &StorageMode) -> PathBuf { - const PROPOSAL_CACHE_FILE_NAME: &str = "current-proposal-cache"; - // Obtain the path to the ledger. - let mut path = aleo_ledger_dir(network, storage_mode); - // Go to the folder right above the ledger when using the default paths, - // otherwise store in the same directory as storage. - if !matches!(storage_mode, StorageMode::Custom(_)) { - path.pop(); - } - // Append the proposal store's file name. - match storage_mode.dev() { - Some(id) => path.push(format!(".{PROPOSAL_CACHE_FILE_NAME}-{network}-{id}")), - None => path.push(format!("{PROPOSAL_CACHE_FILE_NAME}-{network}")), - } - - path +pub fn proposal_cache_path(node_data_dir: &NodeDataDir) -> PathBuf { + node_data_dir.path().join("current-proposal-cache") } /// A helper type for the cache of proposal and signed proposals. @@ -79,15 +65,15 @@ impl ProposalCache { && self.signed_proposals.is_valid(expected_signer) } - /// Returns `true` if a proposal cache exists for the given network and `dev`. - pub fn exists(storage_mode: &StorageMode) -> bool { - proposal_cache_path(N::ID, storage_mode).exists() + /// Returns `true` if a proposal cache given network and `dev`. + pub fn exists(node_data_dir: &NodeDataDir) -> bool { + proposal_cache_path(node_data_dir).exists() } /// Load the proposal cache from the file system and ensure that the proposal cache is valid. - pub fn load(expected_signer: Address, storage_mode: &StorageMode) -> Result { - // Construct the proposal cache file system path. - let path = proposal_cache_path(N::ID, storage_mode); + pub fn load(expected_signer: Address, node_data_dir: &NodeDataDir) -> Result { + // Construct the proposal cache file system pa + let path = proposal_cache_path(node_data_dir); // Deserialize the proposal cache from the file system. let proposal_cache = match fs::read(&path) { @@ -109,8 +95,8 @@ impl ProposalCache { } /// Store the proposal cache to the file system. - pub fn store(&self, storage_mode: &StorageMode) -> Result<()> { - let path = proposal_cache_path(N::ID, storage_mode); + pub fn store(&self, node_data_dir: &NodeDataDir) -> Result<()> { + let path = proposal_cache_path(node_data_dir); info!("Storing the proposal cache to {}...", path.display()); // Serialize the proposal cache. diff --git a/node/bft/src/primary.rs b/node/bft/src/primary.rs index f04389ae72..762954912b 100644 --- a/node/bft/src/primary.rs +++ b/node/bft/src/primary.rs @@ -41,11 +41,14 @@ use crate::{ }, spawn_blocking, }; + use snarkos_account::Account; use snarkos_node_bft_events::PrimaryPing; use snarkos_node_bft_ledger_service::LedgerService; use snarkos_node_network::PeerPoolHandling; use snarkos_node_sync::{BlockSync, DUMMY_SELF_IP, Ping}; +use snarkos_utilities::NodeDataDir; + use snarkvm::{ console::{ prelude::*, @@ -60,7 +63,6 @@ use snarkvm::{ utilities::flatten_error, }; -use aleo_std::StorageMode; use anyhow::Context; use colored::Colorize; use futures::stream::{FuturesUnordered, StreamExt}; @@ -114,8 +116,8 @@ pub struct Primary { handles: Arc>>>, /// The lock for propose_batch. propose_lock: Arc>, - /// The storage mode of the node. - storage_mode: StorageMode, + /// The node configuration directory. + node_data_dir: NodeDataDir, } impl Primary { @@ -132,7 +134,7 @@ impl Primary { ip: Option, trusted_validators: &[SocketAddr], trusted_peers_only: bool, - storage_mode: StorageMode, + node_data_dir: NodeDataDir, dev: Option, ) -> Result { // Initialize the gateway. @@ -143,7 +145,7 @@ impl Primary { ip, trusted_validators, trusted_peers_only, - storage_mode.clone(), + node_data_dir.clone(), dev, )?; // Initialize the sync module. @@ -162,16 +164,16 @@ impl Primary { signed_proposals: Default::default(), handles: Default::default(), propose_lock: Default::default(), - storage_mode, + node_data_dir, }) } /// Load the proposal cache file and update the Primary state with the stored data. async fn load_proposal_cache(&self) -> Result<()> { // Fetch the signed proposals from the file system if it exists. - match ProposalCache::::exists(&self.storage_mode) { + match ProposalCache::::exists(&self.node_data_dir) { // If the proposal cache exists, then process the proposal cache. - true => match ProposalCache::::load(self.gateway.account().address(), &self.storage_mode) { + true => match ProposalCache::::load(self.gateway.account().address(), &self.node_data_dir) { Ok(proposal_cache) => { // Extract the proposal and signed proposals. let (latest_certificate_round, proposed_batch, signed_proposals, pending_certificates) = @@ -1990,7 +1992,7 @@ impl Primary { let pending_certificates = self.storage.get_pending_certificates(); ProposalCache::new(latest_round, proposal, signed_proposals, pending_certificates) }; - if let Err(err) = proposal_cache.store(&self.storage_mode) { + if let Err(err) = proposal_cache.store(&self.node_data_dir) { error!("{}", flatten_error(err.context("Failed to store the current proposal cache"))); } // Close the gateway. @@ -2049,7 +2051,7 @@ mod tests { let account = accounts[account_index].1.clone(); let block_sync = Arc::new(BlockSync::new(ledger.clone())); let mut primary = - Primary::new(account, storage, ledger, block_sync, None, &[], false, StorageMode::new_test(None), None) + Primary::new(account, storage, ledger, block_sync, None, &[], false, NodeDataDir::new_test(None), None) .unwrap(); // Construct a worker instance. diff --git a/node/bft/src/sync/mod.rs b/node/bft/src/sync/mod.rs index d6e4ce2a22..efac5236b0 100644 --- a/node/bft/src/sync/mod.rs +++ b/node/bft/src/sync/mod.rs @@ -1065,7 +1065,8 @@ mod tests { use snarkos_account::Account; use snarkos_node_sync::BlockSync; - use snarkos_utilities::SimpleStoppable; + use snarkos_utilities::{NodeDataDir, SimpleStoppable}; + use snarkvm::{ console::{ account::{Address, PrivateKey}, @@ -1287,7 +1288,7 @@ mod tests { None, &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), None, )?; let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone())); diff --git a/node/bft/tests/common/primary.rs b/node/bft/tests/common/primary.rs index 3e65ec77db..77fbdff2a4 100644 --- a/node/bft/tests/common/primary.rs +++ b/node/bft/tests/common/primary.rs @@ -30,7 +30,7 @@ use snarkos_node_bft::{ use snarkos_node_bft_storage_service::BFTMemoryService; use snarkos_node_network::PeerPoolHandling; use snarkos_node_sync::BlockSync; -use snarkos_utilities::SimpleStoppable; +use snarkos_utilities::{NodeDataDir, SimpleStoppable}; use snarkvm::{ console::{ @@ -180,7 +180,7 @@ impl TestNetwork { Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + id as u16)), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), None, ) .unwrap(); @@ -194,7 +194,7 @@ impl TestNetwork { Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + id as u16)), &[], false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), None, ) .unwrap(); diff --git a/node/bft/tests/common/utils.rs b/node/bft/tests/common/utils.rs index c9ba1d2aec..cf48a9264d 100644 --- a/node/bft/tests/common/utils.rs +++ b/node/bft/tests/common/utils.rs @@ -14,16 +14,15 @@ // limitations under the License. use crate::common::{CurrentNetwork, TranslucentLedgerService, primary}; -use aleo_std::StorageMode; + use snarkos_account::Account; use snarkos_node_bft::{ Gateway, Worker, helpers::{PrimarySender, Storage}, + storage_service::BFTMemoryService, }; - -use snarkos_node_bft::storage_service::BFTMemoryService; -use snarkos_utilities::SimpleStoppable; +use snarkos_utilities::{NodeDataDir, SimpleStoppable}; use snarkvm::{ console::account::Address, @@ -219,7 +218,7 @@ pub fn sample_gateway( ledger: Arc>>, ) -> Gateway { // Initialize the gateway. - Gateway::new(account, storage, ledger, None, &[], false, StorageMode::new_test(None), None).unwrap() + Gateway::new(account, storage, ledger, None, &[], false, NodeDataDir::new_test(None), None).unwrap() } /// Samples a new worker with the given ledger. diff --git a/node/consensus/Cargo.toml b/node/consensus/Cargo.toml index 7d6b75a66a..0ab4772983 100644 --- a/node/consensus/Cargo.toml +++ b/node/consensus/Cargo.toml @@ -79,6 +79,9 @@ features = [ "persistent" ] [dependencies.snarkos-node-sync] workspace = true +[dependencies.snarkos-utilities] +workspace = true + [dependencies.snarkvm] workspace = true diff --git a/node/consensus/src/lib.rs b/node/consensus/src/lib.rs index c72a112f8b..644591bd45 100644 --- a/node/consensus/src/lib.rs +++ b/node/consensus/src/lib.rs @@ -42,6 +42,7 @@ use snarkos_node_bft::{ use snarkos_node_bft_ledger_service::LedgerService; use snarkos_node_bft_storage_service::BFTPersistentStorage; use snarkos_node_sync::{BlockSync, Ping}; +use snarkos_utilities::NodeDataDir; use snarkvm::{ ledger::{ @@ -124,6 +125,7 @@ impl Consensus { trusted_validators: &[SocketAddr], trusted_peers_only: bool, storage_mode: StorageMode, + node_config_dir: NodeDataDir, ping: Arc>, dev: Option, ) -> Result { @@ -142,7 +144,7 @@ impl Consensus { ip, trusted_validators, trusted_peers_only, - storage_mode, + node_config_dir, dev, )?; // Create a new instance of Consensus. diff --git a/node/network/Cargo.toml b/node/network/Cargo.toml index 619c5f09f5..0652089372 100644 --- a/node/network/Cargo.toml +++ b/node/network/Cargo.toml @@ -20,9 +20,6 @@ edition = "2024" locktick = [ "dep:locktick", "snarkos-node-tcp/locktick" ] test = [ ] -[dependencies.aleo-std] -workspace = true - [dependencies.anyhow] workspace = true diff --git a/node/network/src/peering.rs b/node/network/src/peering.rs index f8e0c449e3..fcc33b2e76 100644 --- a/node/network/src/peering.rs +++ b/node/network/src/peering.rs @@ -15,7 +15,6 @@ use crate::{CandidatePeer, ConnectedPeer, ConnectionMode, NodeType, Peer, Resolver, bootstrap_peers}; -use aleo_std::{StorageMode, aleo_ledger_dir}; use snarkos_node_tcp::{P2P, is_bogon_ip, is_unspecified_or_broadcast_ip}; use snarkvm::prelude::{Address, Network}; @@ -33,6 +32,7 @@ use std::{ fs, io::{self, Write}, net::{IpAddr, SocketAddr}, + path::Path, str::FromStr, time::Instant, }; @@ -461,11 +461,8 @@ pub trait PeerPoolHandling: P2P { /// Loads any previously cached peer addresses so they can be introduced as initial /// candidate peers to connect to. - fn load_cached_peers(storage_mode: &StorageMode, filename: &str) -> Result> { - let mut peer_cache_path = aleo_ledger_dir(N::ID, storage_mode); - peer_cache_path.push(filename); - - let peers = match fs::read_to_string(&peer_cache_path) { + fn load_cached_peers(path: &Path) -> Result> { + let peers = match fs::read_to_string(path) { Ok(cached_peers_str) => { let mut cached_peers = Vec::new(); for peer_addr_str in cached_peers_str.lines() { @@ -481,7 +478,7 @@ pub trait PeerPoolHandling: P2P { Vec::new() } Err(error) => { - warn!("{} Couldn't load cached peers at {}: {error}", Self::OWNER, peer_cache_path.display()); + warn!("{} Couldn't load cached peers at {}: {error}", Self::OWNER, path.display()); Vec::new() } }; @@ -491,7 +488,7 @@ pub trait PeerPoolHandling: P2P { /// Preserve the peers who have the greatest known block heights, and the lowest /// number of registered network failures. - fn save_best_peers(&self, storage_mode: &StorageMode, filename: &str, max_entries: Option) -> Result<()> { + fn save_best_peers(&self, path: &Path, max_entries: Option) -> Result<()> { // Collect all prospect peers. let mut peers = self.get_peers(); @@ -513,8 +510,6 @@ pub trait PeerPoolHandling: P2P { } // Dump the connected peers to a file. - let mut path = aleo_ledger_dir(N::ID, storage_mode); - path.push(filename); let mut file = fs::File::create(path)?; for peer in peers { writeln!(file, "{}", peer.listener_addr())?; diff --git a/node/router/Cargo.toml b/node/router/Cargo.toml index a5e91536fc..993d6fa2af 100644 --- a/node/router/Cargo.toml +++ b/node/router/Cargo.toml @@ -28,9 +28,6 @@ metrics = [ "dep:snarkos-node-metrics" ] cuda = [ "snarkvm/cuda", "snarkos-account/cuda" ] serial = [ "snarkos-node-bft-ledger-service/serial" ] -[dependencies.aleo-std] -workspace = true - [dependencies.anyhow] workspace = true @@ -87,6 +84,9 @@ workspace = true [dependencies.snarkos-node-tcp] workspace = true +[dependencies.snarkos-utilities] +workspace = true + [dependencies.snarkvm] workspace = true diff --git a/node/router/src/lib.rs b/node/router/src/lib.rs index 7f50f9cb56..0ff8481cb4 100644 --- a/node/router/src/lib.rs +++ b/node/router/src/lib.rs @@ -24,6 +24,7 @@ extern crate tracing; extern crate snarkos_node_metrics as metrics; pub use snarkos_node_router_messages as messages; +use snarkos_utilities::NodeDataDir; mod handshake; @@ -62,7 +63,6 @@ use snarkos_node_tcp::{Config, ConnectionSide, Tcp}; use snarkvm::prelude::{Address, Network, PrivateKey, ViewKey}; -use aleo_std::StorageMode; use anyhow::Result; #[cfg(feature = "locktick")] use locktick::parking_lot::{Mutex, RwLock}; @@ -74,9 +74,6 @@ use tokio::task::JoinHandle; /// The default port used by the router. pub const DEFAULT_NODE_PORT: u16 = 4130; -/// The name of the file containing cached peers. -const PEER_CACHE_FILENAME: &str = "cached_router_peers"; - /// The router keeps track of connected and connecting peers. /// The actual network communication happens in Inbound/Outbound, /// which is implemented by Validator, Prover, and Client. @@ -137,7 +134,7 @@ pub struct InnerRouter { /// If the flag is set, the node will only connect to trusted peers. trusted_peers_only: bool, /// The storage mode. - storage_mode: StorageMode, + node_data_dir: NodeDataDir, /// The boolean flag for the development mode. is_dev: bool, } @@ -165,7 +162,7 @@ impl Router { trusted_peers: &[SocketAddr], max_peers: u16, trusted_peers_only: bool, - storage_mode: StorageMode, + node_data_dir: NodeDataDir, is_dev: bool, ) -> Result { // Initialize the TCP stack. @@ -176,7 +173,7 @@ impl Router { // Load entries from the peer cache (if present and if we are not in trusted peers only mode). if !trusted_peers_only { - let cached_peers = Self::load_cached_peers(&storage_mode, PEER_CACHE_FILENAME)?; + let cached_peers = Self::load_cached_peers(&node_data_dir.router_peer_cache_path())?; for addr in cached_peers { initial_peers.insert(addr, Peer::new_candidate(addr, false)); } @@ -197,7 +194,7 @@ impl Router { peer_pool: RwLock::new(initial_peers), handles: Default::default(), trusted_peers_only, - storage_mode, + node_data_dir, is_dev, }))) } @@ -279,7 +276,7 @@ impl Router { pub async fn shut_down(&self) { info!("Shutting down the router..."); // Save the best peers for future use. - if let Err(e) = self.save_best_peers(&self.storage_mode, PEER_CACHE_FILENAME, Some(MAX_PEERS_TO_SEND)) { + if let Err(e) = self.save_best_peers(&self.node_data_dir.router_peer_cache_path(), Some(MAX_PEERS_TO_SEND)) { warn!("Failed to persist best peers to disk: {e}"); } // Abort the tasks. diff --git a/node/router/tests/common/mod.rs b/node/router/tests/common/mod.rs index 8b601febed..63902cedff 100644 --- a/node/router/tests/common/mod.rs +++ b/node/router/tests/common/mod.rs @@ -22,11 +22,12 @@ use std::{ sync::Arc, }; -use aleo_std::StorageMode; use snarkos_account::Account; use snarkos_node_bft_ledger_service::MockLedgerService; use snarkos_node_network::NodeType; use snarkos_node_router::Router; +use snarkos_utilities::NodeDataDir; + use snarkvm::{ prelude::{FromBytes, MainnetV0 as CurrentNetwork, Network, PrivateKey, block::Block}, utilities::TestRng, @@ -68,7 +69,7 @@ pub async fn client(listening_port: u16, max_peers: u16, rng: &mut TestRng) -> T &[], max_peers, false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), true, ) .await @@ -89,7 +90,7 @@ pub async fn prover(listening_port: u16, max_peers: u16, rng: &mut TestRng) -> T &[], max_peers, false, - StorageMode::new_test(None), + NodeDataDir::new_test(None), true, ) .await @@ -116,7 +117,7 @@ pub async fn validator( trusted_peers, max_peers, trusted_peers_only, - StorageMode::new_test(None), + NodeDataDir::new_test(None), true, ) .await diff --git a/node/src/client/mod.rs b/node/src/client/mod.rs index faa54886b1..ec3832ee7e 100644 --- a/node/src/client/mod.rs +++ b/node/src/client/mod.rs @@ -37,7 +37,7 @@ use snarkos_node_tcp::{ P2P, protocols::{Disconnect, Handshake, OnConnect, Reading}, }; -use snarkos_utilities::{SignalHandler, Stoppable}; +use snarkos_utilities::{NodeDataDir, SignalHandler, Stoppable}; use snarkvm::{ console::network::Network, @@ -143,6 +143,7 @@ impl> Client { genesis: Block, cdn: Option, storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev: Option, signal_handler: Arc, @@ -167,7 +168,7 @@ impl> Client { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, trusted_peers_only, - storage_mode.clone(), + node_data_dir.clone(), dev.is_some(), ) .await?; diff --git a/node/src/node.rs b/node/src/node.rs index 93ffeb6453..dcc1e3297a 100644 --- a/node/src/node.rs +++ b/node/src/node.rs @@ -24,7 +24,7 @@ use crate::{ }; use snarkos_account::Account; -use snarkos_utilities::SignalHandler; +use snarkos_utilities::{NodeDataDir, SignalHandler}; use snarkvm::prelude::{ Address, @@ -71,6 +71,7 @@ impl Node { genesis: Block, cdn: Option, storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev_txs: bool, dev: Option, @@ -88,6 +89,7 @@ impl Node { genesis, cdn, storage_mode, + node_data_dir, trusted_peers_only, dev_txs, dev, @@ -103,7 +105,7 @@ impl Node { account: Account, trusted_peers: &[SocketAddr], genesis: Block, - storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev: Option, signal_handler: Arc, @@ -114,7 +116,7 @@ impl Node { account, trusted_peers, genesis, - storage_mode, + node_data_dir, trusted_peers_only, dev, signal_handler, @@ -133,6 +135,7 @@ impl Node { genesis: Block, cdn: Option, storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev: Option, signal_handler: Arc, @@ -147,6 +150,7 @@ impl Node { genesis, cdn, storage_mode, + node_data_dir, trusted_peers_only, dev, signal_handler, diff --git a/node/src/prover/mod.rs b/node/src/prover/mod.rs index 5cedb19aeb..477dcd669e 100644 --- a/node/src/prover/mod.rs +++ b/node/src/prover/mod.rs @@ -23,7 +23,6 @@ use crate::{ use snarkos_account::Account; use snarkos_node_network::{NodeType, PeerPoolHandling}; - use snarkos_node_router::{ Heartbeat, Inbound, @@ -36,7 +35,7 @@ use snarkos_node_tcp::{ P2P, protocols::{Disconnect, Handshake, OnConnect, Reading}, }; -use snarkos_utilities::{SignalHandler, Stoppable}; +use snarkos_utilities::{NodeDataDir, SignalHandler, Stoppable}; use snarkvm::{ ledger::narwhal::Data, @@ -49,7 +48,6 @@ use snarkvm::{ synthesizer::VM, }; -use aleo_std::StorageMode; use anyhow::Result; use colored::Colorize; use core::{marker::PhantomData, time::Duration}; @@ -104,7 +102,7 @@ impl> Prover { account: Account, trusted_peers: &[SocketAddr], genesis: Block, - storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev: Option, signal_handler: Arc, @@ -121,7 +119,7 @@ impl> Prover { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, trusted_peers_only, - storage_mode, + node_data_dir, dev.is_some(), ) .await?; diff --git a/node/src/validator/mod.rs b/node/src/validator/mod.rs index c3d75d6e9e..014ab410e2 100644 --- a/node/src/validator/mod.rs +++ b/node/src/validator/mod.rs @@ -36,7 +36,7 @@ use snarkos_node_tcp::{ P2P, protocols::{Disconnect, Handshake, OnConnect, Reading}, }; -use snarkos_utilities::SignalHandler; +use snarkos_utilities::{NodeDataDir, SignalHandler}; use snarkvm::prelude::{ Ledger, @@ -88,6 +88,7 @@ impl> Validator { genesis: Block, cdn: Option, storage_mode: StorageMode, + node_data_dir: NodeDataDir, trusted_peers_only: bool, dev_txs: bool, dev: Option, @@ -114,7 +115,7 @@ impl> Validator { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, trusted_peers_only, - storage_mode.clone(), + node_data_dir.clone(), dev.is_some(), ) .await?; @@ -133,6 +134,7 @@ impl> Validator { trusted_validators, trusted_peers_only, storage_mode.clone(), + node_data_dir.clone(), ping.clone(), dev, ) @@ -498,6 +500,7 @@ mod tests { let node = SocketAddr::from_str("0.0.0.0:4130").unwrap(); let rest = SocketAddr::from_str("0.0.0.0:3030").unwrap(); let storage_mode = StorageMode::Development(0); + let node_config_dir = NodeDataDir::new_development(0); let dev_txs = true; // Initialize an (insecure) fixed RNG. @@ -524,6 +527,7 @@ mod tests { genesis, None, storage_mode, + node_config_dir, false, dev_txs, None, diff --git a/node/tests/common/node.rs b/node/tests/common/node.rs index 2e55778e88..fe419ee8f2 100644 --- a/node/tests/common/node.rs +++ b/node/tests/common/node.rs @@ -17,7 +17,7 @@ use crate::common::test_peer::sample_genesis_block; use snarkos_account::Account; use snarkos_node::{Client, Prover, Validator}; -use snarkos_utilities::SignalHandler; +use snarkos_utilities::{NodeDataDir, SignalHandler}; use snarkvm::prelude::{MainnetV0 as CurrentNetwork, store::helpers::memory::ConsensusMemory}; @@ -34,6 +34,7 @@ pub async fn client() -> Client> sample_genesis_block(), None, // No CDN. StorageMode::new_test(None), + NodeDataDir::new_test(None), false, // Connect to untrusted peers. None, SignalHandler::new(), @@ -48,7 +49,7 @@ pub async fn prover() -> Prover> Account::::from_str("APrivateKey1zkp2oVPTci9kKcUprnbzMwq95Di1MQERpYBhEeqvkrDirK1").unwrap(), &[], sample_genesis_block(), - StorageMode::new_test(None), + NodeDataDir::new_test(None), false, None, SignalHandler::new(), @@ -69,6 +70,7 @@ pub async fn validator() -> Validator(address: &D) -> PathBuf { + PathBuf::from(format!("jwt_secret_{address}.txt")) +} + +/// The old filename of the current proposal cache. +pub fn legacy_current_proposal_cache_file(network: u16, dev: Option) -> PathBuf { + if let Some(dev) = dev { + PathBuf::from(format!(".current-proposal-cache-{network}-{dev}")) + } else { + PathBuf::from(format!("current-proposal-cache-{network}")) + } +} + +/// Tracks information about where the node-specfic configuration files are stored. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeDataDir { + path: PathBuf, + + /// Is the node a development node? + /// This will eventually be removed when the development storage format is no longer supported. + dev: Option, +} + +impl NodeDataDir { + pub fn new(path: PathBuf) -> Self { + Self { path, dev: None } + } + + pub fn new_test(dev: Option) -> Self { + if let Some(dev) = dev { + Self { path: PathBuf::from(format!(".node-config-test-{dev}")), dev: Some(dev) } + } else { + Self { path: PathBuf::from(".node-config-test"), dev: None } + } + } + + pub fn new_development(dev: u16) -> Self { + Self { path: PathBuf::from(format!(".node-config-{dev}")), dev: Some(dev) } + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn is_dev(&self) -> bool { + self.dev.is_some() + } + + pub fn dev(&self) -> Option { + self.dev + } + + /// The location to store the previous peer cache. + pub fn router_peer_cache_path(&self) -> PathBuf { + self.path.join(ROUTER_PEER_CACHE_FILE) + } + + pub fn gateway_peer_cache_path(&self) -> PathBuf { + self.path.join(GATWAY_PEER_CACHE_FILE) + } + + /// The location to store the current proposal cache. + pub fn current_proposal_cache_path(&self) -> PathBuf { + self.path.join(CURRENT_PROPOSAL_CACHE_FILE) + } + + /// The location to store the JWT secret for a given address. + pub fn jwt_secret_path(&self, address: &D) -> PathBuf { + self.path.join(jwt_secret_file(address)) + } +} diff --git a/utilities/src/signals.rs b/utilities/src/signals.rs index 83809d1b2a..3c5ed63446 100644 --- a/utilities/src/signals.rs +++ b/utilities/src/signals.rs @@ -22,6 +22,7 @@ use std::sync::{ Arc, atomic::{AtomicBool, Ordering}, }; + use tokio::sync::oneshot; use tracing::{debug, error, trace}; @@ -62,7 +63,7 @@ impl Stoppable for SimpleStoppable { /// Helper for signal handling that implements the `Stoppable` trait. /// /// This struct will set itself to "stopped" as soon as the process receives Ctrl+C. -/// It can also be manually stopped (e.g., when the node encounters a fatal error). +/// It can also be manually stopped (e.g., when the node encounters a fatal error) pub struct SignalHandler { /// This sender is used to notify a waiting task that the node has been stopped. /// If this is `None`, the node is in the process of shutting down. From c49f2fab1b7379e2c2e1600a6b9081ff39db3e44 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Tue, 23 Dec 2025 18:35:18 +0100 Subject: [PATCH 02/13] ci: bump `cargo audit` version --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e1f9abf351..2a9ed982eb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -588,14 +588,16 @@ jobs: resource_class: << pipeline.parameters.medium >> steps: - checkout + # Use nightly until we bump the MSRV to 1.89.0 or greater + - install_rust_nightly - setup_environment: cache_key: v4.2.0-rust-1.88.0-cargo-audit-cache - run: name: Check for security vulnerabilities no_output_timeout: 10m command: | - cargo install cargo-audit@0.21.2 --locked - cargo audit -D warnings + cargo +nightly install cargo-audit@0.22.0 --locked + cargo +nightly audit -D warnings - clear_environment: cache_key: v4.2.0-rust-1.88.0-cargo-audit-cache From 743f6dfe440d912174cbaeb8bdb0a0c8019ca703 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Wed, 24 Dec 2025 13:49:54 +0100 Subject: [PATCH 03/13] feat(cli): add auto migration feature --- README.md | 14 ++++++++ cli/src/commands/start.rs | 76 ++++++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e68c06c6eb..eaa0560812 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,20 @@ APrivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Note, using the `--raw` flag with the command will sign plaintext messages as bytes rather than [Aleo values](https://developer.aleo.org/guides/aleo/language#data-types-and-values) such as `1u8` or `100field`. +### 5. What the different between `node-data` and `ledger`? +Ledger contains only public ledger information, while `node-data` contains information specific to the node that created it. The latter should not be shared with untrusted sources, and, for validators, contains data required to participate in consensus. + +### 6. At startup I get an error telling me the nodes still uses the old storage format. What should I do? +The node should have created a new folder for the node data. For example, for mainnet a folder should exist at `~/.aleo/storage/node-data-0`. + +The error message will tell you what data to migrate. Alternatively, you can start the node with `--auto-migrate-node-data` and it will attempt to do this automatically. + +The following is an overview of all files that may be needed to be migrated. +- The JSON Web token secret, located at `~/.aleo/storage/jwt_secrect_{address}.txt`. Move it to `~/.aleo/storage/node-data-0/jwt_secret_{address}.txt`. + Note, if you are running different nodes with different addresses there may be multiple of these. The error message will tell you which one to migrate. +- The router peer cache located at `~/.aleo/storage/ledger-0/cache_router_peers`. Migrate it to `~/.aleo/storage/node-data-0/router-peer-cache`. +- The gateway peer cache located at `~/.aleo/storage/ledger-0/cache_gateway_peers` (only for validators). Migrate it to `~/.aleo/storage/node-data-0/gateway-peer-cache`. +- The latest proposal cache located at `~/.aleo/storage/current-proposal-cache-0` (only for validators). Migrate it to `~/.aleo/storage/node-data-0/current-proposal-cache`. ## 5. Command Line Interface diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index cd5cfd0e40..c2699a80bc 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -57,6 +57,7 @@ use rand_chacha::ChaChaRng; use serde::{Deserialize, Serialize}; use std::{ + fs, net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs}, path::{Path, PathBuf}, sync::{Arc, atomic::AtomicBool}, @@ -285,6 +286,10 @@ pub struct Start { /// If development mode is enabled, specify the custom bonded balances as a JSON object. #[clap(long, group = "dev_flags")] pub dev_bonded_balances: Option, + + /// If the flag is set, the node will attempt to automatically migrate the node data to the new format. + #[clap(long)] + pub auto_migrate_node_data: bool, } impl Start { @@ -737,7 +742,7 @@ impl Start { // Checks for the old storage format and prints instructions to migrate. // We perform this check after creating the node data directory, so that migrating the data is easier. - Self::check_for_old_storage_format(&aleo_ledger_dir(N::ID, &storage_mode), &account.address(), &node_data_dir, self.dev).with_context(|| "Node still uses the old storage format")?; + Self::check_for_old_storage_format(&aleo_ledger_dir(N::ID, &storage_mode), &account.address(), &node_data_dir, self.dev, self.auto_migrate_node_data).with_context(|| "Node still uses the old storage format.")?; // Compute the optional REST server JWT. let jwt_token = if self.nojwt { @@ -845,6 +850,7 @@ impl Start { address: &Address, node_data_dir: &NodeDataDir, dev: Option, + auto_migrate: bool, ) -> Result<()> { let ledger_parent_dir = ledger_dir.parent().unwrap(); @@ -854,24 +860,62 @@ impl Start { let old_proposal_cache_path = ledger_dir.join(node_data::legacy_current_proposal_cache_file(N::ID, dev)); let old_jwt_secret_path = ledger_parent_dir.join(node_data::jwt_secret_file(address)); - if old_router_cache_path.exists() { - let new_router_cache_path = node_data_dir.router_peer_cache_path(); - bail!("Please migrate {old_router_cache_path:?} to {new_router_cache_path:?} before restarting."); - } + if auto_migrate { + if old_router_cache_path.exists() { + let new_router_cache_path = node_data_dir.router_peer_cache_path(); + info!("Migrating node data file \"{old_router_cache_path:?}\" to \"{new_router_cache_path:?}\""); + fs::rename(old_router_cache_path, new_router_cache_path) + .with_context(|| "Failed to migrate node data file")?; + } - if old_gatway_cache_path.exists() { - let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); - bail!("Please migrate {old_gatway_cache_path:?} to {new_gateway_cache_path:?} before restarting."); - } + if old_gatway_cache_path.exists() { + let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); + info!("Migrating node data file \"{old_gatway_cache_path:?}\" to \"{new_gateway_cache_path:?}\""); + fs::rename(old_gatway_cache_path, new_gateway_cache_path) + .with_context(|| "Failed to migrate node data file")?; + } - if old_proposal_cache_path.exists() { - let new_proposal_cache_path = node_data_dir.current_proposal_cache_path(); - bail!("Please migrate {old_proposal_cache_path:?} to {new_proposal_cache_path:?} before restarting."); - } + if old_proposal_cache_path.exists() { + let new_proposal_cache_path = node_data_dir.current_proposal_cache_path(); + info!("Migrating node data file \"{old_proposal_cache_path:?}\" to \"{new_proposal_cache_path:?}\""); + fs::rename(old_proposal_cache_path, new_proposal_cache_path) + .with_context(|| "Failed to migrate node data file")?; + } - if old_jwt_secret_path.exists() { - let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address); - bail!("Please migrate {old_jwt_secret_path:?} to {new_jwt_secret_path:?} before restarting."); + if old_jwt_secret_path.exists() { + let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address); + info!("Migrating node data file \"{old_jwt_secret_path:?}\" to \"{new_jwt_secret_path:?}\""); + fs::rename(old_jwt_secret_path, new_jwt_secret_path) + .with_context(|| "Failed to migrate node data file")?; + } + } else { + if old_router_cache_path.exists() { + let new_router_cache_path = node_data_dir.router_peer_cache_path(); + bail!( + "Please migrate the node data file \"{old_router_cache_path:?}\" to \"{new_router_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." + ); + } + + if old_gatway_cache_path.exists() { + let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); + bail!( + "Please migrate the node data file \"{old_gatway_cache_path:?}\" to \"{new_gateway_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." + ); + } + + if old_proposal_cache_path.exists() { + let new_proposal_cache_path = node_data_dir.current_proposal_cache_path(); + bail!( + "Please migrate the node data file \"{old_proposal_cache_path:?}\" to \"{new_proposal_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." + ); + } + + if old_jwt_secret_path.exists() { + let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address); + bail!( + "Please migrate the node data file \"{old_jwt_secret_path:?}\" to \"{new_jwt_secret_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." + ); + } } Ok(()) From 8465a7b913e8d56cbf1eefc82eed01e7ef1a6f19 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Fri, 2 Jan 2026 09:40:04 -0800 Subject: [PATCH 04/13] misc: update .gitignore --- .gitignore | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 96ac6cd91a..c96ff6a4e6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,19 @@ wasm/Cargo.lock **/build **.history-* -**.ledger-* -**.current-proposal-cache-* **.logs-* validator-* -**.bft-storage-*/ **proptest-regressions/ **package-lock.json **node_modules/ jwt_secret*.txt + +# Files created by a running snarkOS node +**.ledger-* +**node-data-* + +# Legacy files created by a running snarkOS node +**.current-proposal-cache-* +**cached_gateway_peers +**cached_router_peers +**.bft-storage-*/ From 03c797e3ac499f74d3a66c850054dbc03d4a77cf Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Fri, 2 Jan 2026 11:32:07 -0800 Subject: [PATCH 05/13] misc: fix misspellings of 'gateway' --- cli/src/commands/start.rs | 12 ++++++------ node/bft/src/gateway.rs | 2 +- utilities/src/node_data.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index c2699a80bc..72b461117d 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -856,7 +856,7 @@ impl Start { // Determine the old paths used for node configuration files. let old_router_cache_path = ledger_dir.join(node_data::LEGACY_ROUTER_PEER_CACHE_FILE); - let old_gatway_cache_path = ledger_dir.join(node_data::LEGACY_GATWAY_PEER_CACHE_FILE); + let old_gateway_cache_path = ledger_dir.join(node_data::LEGACY_GATEWAY_PEER_CACHE_FILE); let old_proposal_cache_path = ledger_dir.join(node_data::legacy_current_proposal_cache_file(N::ID, dev)); let old_jwt_secret_path = ledger_parent_dir.join(node_data::jwt_secret_file(address)); @@ -868,10 +868,10 @@ impl Start { .with_context(|| "Failed to migrate node data file")?; } - if old_gatway_cache_path.exists() { + if old_gateway_cache_path.exists() { let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); - info!("Migrating node data file \"{old_gatway_cache_path:?}\" to \"{new_gateway_cache_path:?}\""); - fs::rename(old_gatway_cache_path, new_gateway_cache_path) + info!("Migrating node data file \"{old_gateway_cache_path:?}\" to \"{new_gateway_cache_path:?}\""); + fs::rename(old_gateway_cache_path, new_gateway_cache_path) .with_context(|| "Failed to migrate node data file")?; } @@ -896,10 +896,10 @@ impl Start { ); } - if old_gatway_cache_path.exists() { + if old_gateway_cache_path.exists() { let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path(); bail!( - "Please migrate the node data file \"{old_gatway_cache_path:?}\" to \"{new_gateway_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." + "Please migrate the node data file \"{old_gateway_cache_path:?}\" to \"{new_gateway_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`." ); } diff --git a/node/bft/src/gateway.rs b/node/bft/src/gateway.rs index a313837f57..2137850a1e 100644 --- a/node/bft/src/gateway.rs +++ b/node/bft/src/gateway.rs @@ -842,7 +842,7 @@ impl Gateway { pub async fn shut_down(&self) { info!("Shutting down the gateway..."); // Save the best peers for future use. - if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, true) { + if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, false) { warn!("Failed to persist best validators to disk: {e}"); } // Abort the tasks. diff --git a/utilities/src/node_data.rs b/utilities/src/node_data.rs index fba93f0dc8..53abda8e61 100644 --- a/utilities/src/node_data.rs +++ b/utilities/src/node_data.rs @@ -16,9 +16,9 @@ use std::path::{Path, PathBuf}; /// The filename of the gateway peer cache. -pub const GATWAY_PEER_CACHE_FILE: &str = "gateway-peer-cache"; +pub const GATEWAY_PEER_CACHE_FILE: &str = "gateway-peer-cache"; /// The old filename of the gateway peer cache. -pub const LEGACY_GATWAY_PEER_CACHE_FILE: &str = "cached_gateway_peers"; +pub const LEGACY_GATEWAY_PEER_CACHE_FILE: &str = "cached_gateway_peers"; /// The filename of the router peer cache. pub const ROUTER_PEER_CACHE_FILE: &str = "router-peer-cache"; @@ -89,7 +89,7 @@ impl NodeDataDir { } pub fn gateway_peer_cache_path(&self) -> PathBuf { - self.path.join(GATWAY_PEER_CACHE_FILE) + self.path.join(GATEWAY_PEER_CACHE_FILE) } pub fn validator_whitelist_path(&self) -> PathBuf { From 5b4072990ce19468708153064681b274f778fa34 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Fri, 2 Jan 2026 11:45:50 -0800 Subject: [PATCH 06/13] misc(utils): remove dev field from 'NodeDataDir' --- README.md | 2 +- cli/src/commands/clean.rs | 6 ++++-- utilities/src/node_data.rs | 20 ++++---------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index eaa0560812..a04cf60216 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ APrivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Note, using the `--raw` flag with the command will sign plaintext messages as bytes rather than [Aleo values](https://developer.aleo.org/guides/aleo/language#data-types-and-values) such as `1u8` or `100field`. -### 5. What the different between `node-data` and `ledger`? +### 5. What is the different between `node-data` and `ledger`? Ledger contains only public ledger information, while `node-data` contains information specific to the node that created it. The latter should not be shared with untrusted sources, and, for validators, contains data required to participate in consensus. ### 6. At startup I get an error telling me the nodes still uses the old storage format. What should I do? diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs index c24f907e0b..c5c41884d1 100644 --- a/cli/src/commands/clean.rs +++ b/cli/src/commands/clean.rs @@ -31,9 +31,11 @@ pub struct Clean { /// Specify the network to remove from storage (0 = mainnet, 1 = testnet, 2 = canary) #[clap(default_value_t=MainnetV0::ID, long = "network", value_parser = clap::value_parser!(u16).range((MainnetV0::ID as i64)..=(CanaryV0::ID as i64)))] pub network: u16, + /// Enables development mode, specify the unique ID of the local node to clean. #[clap(long)] pub dev: Option, + /// Specify the path to a directory containing the ledger. Overrides the default path (also for /// dev). #[clap(long = "path")] @@ -41,14 +43,14 @@ pub struct Clean { /// Sets a custom path for the node configuration. #[clap(long)] - pub node_config_dir: Option, + pub node_data_path: Option, } impl Clean { /// Cleans the snarkOS node storage. pub fn parse(self) -> Result { // Remove the specified node configuration from storage. - let node_config_dir = parse_node_data_dir(&self.node_config_dir, self.network, self.dev)?; + let node_config_dir = parse_node_data_dir(&self.node_data_path, self.network, self.dev)?; println!("{}", Self::remove_node_data(&node_config_dir)?); // Remove the specified ledger from storage. diff --git a/utilities/src/node_data.rs b/utilities/src/node_data.rs index 53abda8e61..03e834d25c 100644 --- a/utilities/src/node_data.rs +++ b/utilities/src/node_data.rs @@ -48,41 +48,29 @@ pub fn legacy_current_proposal_cache_file(network: u16, dev: Option) -> Pat #[derive(Clone, Debug, PartialEq, Eq)] pub struct NodeDataDir { path: PathBuf, - - /// Is the node a development node? - /// This will eventually be removed when the development storage format is no longer supported. - dev: Option, } impl NodeDataDir { pub fn new(path: PathBuf) -> Self { - Self { path, dev: None } + Self { path } } pub fn new_test(dev: Option) -> Self { if let Some(dev) = dev { - Self { path: PathBuf::from(format!(".node-config-test-{dev}")), dev: Some(dev) } + Self { path: PathBuf::from(format!(".node-config-test-{dev}")) } } else { - Self { path: PathBuf::from(".node-config-test"), dev: None } + Self { path: PathBuf::from(".node-config-test") } } } pub fn new_development(dev: u16) -> Self { - Self { path: PathBuf::from(format!(".node-config-{dev}")), dev: Some(dev) } + Self { path: PathBuf::from(format!(".node-config-{dev}")) } } pub fn path(&self) -> &Path { &self.path } - pub fn is_dev(&self) -> bool { - self.dev.is_some() - } - - pub fn dev(&self) -> Option { - self.dev - } - /// The location to store the previous peer cache. pub fn router_peer_cache_path(&self) -> PathBuf { self.path.join(ROUTER_PEER_CACHE_FILE) From 761134eb579991707e614ae530c7ac4ff0bbe02a Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Fri, 2 Jan 2026 12:23:52 -0800 Subject: [PATCH 07/13] misc: rename all remaining instances of 'node-config' to 'node-data' --- cli/src/commands/clean.rs | 4 ++-- cli/src/helpers/args.rs | 4 ++-- node/bft/examples/simple_node.rs | 8 ++++---- node/bft/src/bft.rs | 4 ++-- node/consensus/src/lib.rs | 4 ++-- node/src/validator/mod.rs | 4 ++-- utilities/src/node_data.rs | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs index c5c41884d1..ede52d1746 100644 --- a/cli/src/commands/clean.rs +++ b/cli/src/commands/clean.rs @@ -50,8 +50,8 @@ impl Clean { /// Cleans the snarkOS node storage. pub fn parse(self) -> Result { // Remove the specified node configuration from storage. - let node_config_dir = parse_node_data_dir(&self.node_data_path, self.network, self.dev)?; - println!("{}", Self::remove_node_data(&node_config_dir)?); + let node_data_dir = parse_node_data_dir(&self.node_data_path, self.network, self.dev)?; + println!("{}", Self::remove_node_data(&node_data_dir)?); // Remove the specified ledger from storage. let storage_mode = match self.path { diff --git a/cli/src/helpers/args.rs b/cli/src/helpers/args.rs index f7956a9639..bf4450c449 100644 --- a/cli/src/helpers/args.rs +++ b/cli/src/helpers/args.rs @@ -79,11 +79,11 @@ pub(crate) fn parse_private_key( /// Returns the path to the node configuration directory. pub(crate) fn parse_node_data_dir( - node_config_dir: &Option, + node_data_dir: &Option, network: u16, dev: Option, ) -> Result { - if let Some(custom) = node_config_dir { + if let Some(custom) = node_data_dir { return Ok(NodeDataDir::new(custom.clone())); } diff --git a/node/bft/examples/simple_node.rs b/node/bft/examples/simple_node.rs index a7ad7d710d..fe4bd4bc27 100644 --- a/node/bft/examples/simple_node.rs +++ b/node/bft/examples/simple_node.rs @@ -140,7 +140,7 @@ pub async fn start_bft( Some(ip) => Some(*ip), None => Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + node_id)), }; - let node_config_dir = NodeDataDir::new_test(None); + let node_data_dir = NodeDataDir::new_test(None); // Initialize the trusted validators. let trusted_validators = trusted_validators(node_id, num_nodes, peers); let trusted_peers_only = false; @@ -158,7 +158,7 @@ pub async fn start_bft( ip, &trusted_validators, trusted_peers_only, - node_config_dir, + node_data_dir, None, )?; // Run the BFT instance. @@ -194,7 +194,7 @@ pub async fn start_primary( Some(ip) => Some(*ip), None => Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + node_id)), }; - let node_config_dir = NodeDataDir::new_test(None); + let node_data_dir = NodeDataDir::new_test(None); // Initialize the trusted validators. let trusted_validators = trusted_validators(node_id, num_nodes, peers); let trusted_peers_only = false; @@ -208,7 +208,7 @@ pub async fn start_primary( ip, &trusted_validators, trusted_peers_only, - node_config_dir, + node_data_dir, None, )?; // Run the primary instance. diff --git a/node/bft/src/bft.rs b/node/bft/src/bft.rs index a8f7cd5326..f85a1756a4 100644 --- a/node/bft/src/bft.rs +++ b/node/bft/src/bft.rs @@ -101,7 +101,7 @@ impl BFT { ip: Option, trusted_validators: &[SocketAddr], trusted_peers_only: bool, - node_config_dir: NodeDataDir, + node_data_dir: NodeDataDir, dev: Option, ) -> Result { Ok(Self { @@ -113,7 +113,7 @@ impl BFT { ip, trusted_validators, trusted_peers_only, - node_config_dir, + node_data_dir, dev, )?, dag: Default::default(), diff --git a/node/consensus/src/lib.rs b/node/consensus/src/lib.rs index 644591bd45..c34e71d8e4 100644 --- a/node/consensus/src/lib.rs +++ b/node/consensus/src/lib.rs @@ -125,7 +125,7 @@ impl Consensus { trusted_validators: &[SocketAddr], trusted_peers_only: bool, storage_mode: StorageMode, - node_config_dir: NodeDataDir, + node_data_dir: NodeDataDir, ping: Arc>, dev: Option, ) -> Result { @@ -144,7 +144,7 @@ impl Consensus { ip, trusted_validators, trusted_peers_only, - node_config_dir, + node_data_dir, dev, )?; // Create a new instance of Consensus. diff --git a/node/src/validator/mod.rs b/node/src/validator/mod.rs index 014ab410e2..58bfb345d7 100644 --- a/node/src/validator/mod.rs +++ b/node/src/validator/mod.rs @@ -500,7 +500,7 @@ mod tests { let node = SocketAddr::from_str("0.0.0.0:4130").unwrap(); let rest = SocketAddr::from_str("0.0.0.0:3030").unwrap(); let storage_mode = StorageMode::Development(0); - let node_config_dir = NodeDataDir::new_development(0); + let node_data_dir = NodeDataDir::new_development(0); let dev_txs = true; // Initialize an (insecure) fixed RNG. @@ -527,7 +527,7 @@ mod tests { genesis, None, storage_mode, - node_config_dir, + node_data_dir, false, dev_txs, None, diff --git a/utilities/src/node_data.rs b/utilities/src/node_data.rs index 03e834d25c..a9e7712088 100644 --- a/utilities/src/node_data.rs +++ b/utilities/src/node_data.rs @@ -57,14 +57,14 @@ impl NodeDataDir { pub fn new_test(dev: Option) -> Self { if let Some(dev) = dev { - Self { path: PathBuf::from(format!(".node-config-test-{dev}")) } + Self { path: PathBuf::from(format!(".node-data-test-{dev}")) } } else { - Self { path: PathBuf::from(".node-config-test") } + Self { path: PathBuf::from(".node-data-test") } } } pub fn new_development(dev: u16) -> Self { - Self { path: PathBuf::from(format!(".node-config-{dev}")) } + Self { path: PathBuf::from(format!(".node-data-{dev}")) } } pub fn path(&self) -> &Path { From 6b59e62f13005e2459002fde02f0ce9d98982be0 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Fri, 2 Jan 2026 12:35:29 -0800 Subject: [PATCH 08/13] misc(util): simplify development mode for NodeDataDir --- cli/src/helpers/args.rs | 9 +-------- node/src/validator/mod.rs | 2 +- utilities/src/node_data.rs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cli/src/helpers/args.rs b/cli/src/helpers/args.rs index bf4450c449..7d8d931660 100644 --- a/cli/src/helpers/args.rs +++ b/cli/src/helpers/args.rs @@ -88,14 +88,7 @@ pub(crate) fn parse_node_data_dir( } let config = if let Some(dev) = dev { - // Development mode - let mut path = match std::env::current_dir() { - Ok(current_dir) => current_dir, - _ => PathBuf::from(env!("CARGO_MANIFEST_DIR")), - }; - - path.push(format!(".node-data-{network}-{dev}")); - NodeDataDir::new(path) + NodeDataDir::new_development(network, dev) } else { // Production mode let path = aleo_dir().join("storage").join(format!("node-data-{network}")); diff --git a/node/src/validator/mod.rs b/node/src/validator/mod.rs index 58bfb345d7..fea065f00c 100644 --- a/node/src/validator/mod.rs +++ b/node/src/validator/mod.rs @@ -500,7 +500,7 @@ mod tests { let node = SocketAddr::from_str("0.0.0.0:4130").unwrap(); let rest = SocketAddr::from_str("0.0.0.0:3030").unwrap(); let storage_mode = StorageMode::Development(0); - let node_data_dir = NodeDataDir::new_development(0); + let node_data_dir = NodeDataDir::new_development(CurrentNetwork::ID, 0); let dev_txs = true; // Initialize an (insecure) fixed RNG. diff --git a/utilities/src/node_data.rs b/utilities/src/node_data.rs index a9e7712088..ec8bce93a8 100644 --- a/utilities/src/node_data.rs +++ b/utilities/src/node_data.rs @@ -51,10 +51,12 @@ pub struct NodeDataDir { } impl NodeDataDir { + /// Initializes the node data directory the given path. pub fn new(path: PathBuf) -> Self { Self { path } } + /// Initializes the node data directory to a location suitable for unit/integration tests. pub fn new_test(dev: Option) -> Self { if let Some(dev) = dev { Self { path: PathBuf::from(format!(".node-data-test-{dev}")) } @@ -63,8 +65,15 @@ impl NodeDataDir { } } - pub fn new_development(dev: u16) -> Self { - Self { path: PathBuf::from(format!(".node-data-{dev}")) } + /// Initializes the node data directory path to the development path for the specified network and node index. + pub fn new_development(network: u16, dev: u16) -> Self { + // Use the current directory as the base path, and fall back to the + // cargo manifest directory if the current directory is not available. + let path = std::env::current_dir() + .unwrap_or(PathBuf::from(env!("CARGO_MANIFEST_DIR"))) + .join(format!(".node-data-{network}-{dev}")); + + Self::new(path) } pub fn path(&self) -> &Path { From 015a099c86184d44aa838415af3c0d0cef938b02 Mon Sep 17 00:00:00 2001 From: vicsn Date: Wed, 7 Jan 2026 13:10:02 +0100 Subject: [PATCH 09/13] Update node/bft/src/gateway.rs Co-authored-by: ljedrz <3750347+ljedrz@users.noreply.github.com> Signed-off-by: vicsn --- node/bft/src/gateway.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/bft/src/gateway.rs b/node/bft/src/gateway.rs index 2137850a1e..a313837f57 100644 --- a/node/bft/src/gateway.rs +++ b/node/bft/src/gateway.rs @@ -842,7 +842,7 @@ impl Gateway { pub async fn shut_down(&self) { info!("Shutting down the gateway..."); // Save the best peers for future use. - if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, false) { + if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, true) { warn!("Failed to persist best validators to disk: {e}"); } // Abort the tasks. From 9502000fb05334d137783fbca417831f0ed481fe Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 8 Jan 2026 12:09:16 -0800 Subject: [PATCH 10/13] misc(cli): deny validator start if custom path is set for ledger but not node_data --- cli/src/commands/start.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 72b461117d..89fd68182a 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -719,10 +719,21 @@ impl Start { }, }; + // Users may have unintentionally set a custom path for the ledger, but not for the node data. + // For validators, we make this an errors, so important files like the proposal cache are stored at the location + // exepcted by the node operator. if self.node_data.is_some() && !matches!(storage_mode, StorageMode::Custom(_)) { - warn!("Custom path set for `--storage`, but not for `--node-data`. The latter will use the default path."); + if node_type == NodeType::Validator { + bail!("Custom path set for `--storage`, but not for `--node-data`.") + } else { + warn!("Custom path set for `--storage`, but not for `--node-data`. The latter will use the default path."); + } } else if matches!(storage_mode, StorageMode::Custom(_)) && self.node_data.is_none() { - warn!("Custom path set for `--storage`, but `--node-data` is not set. The latter will use the default path."); + if node_type == NodeType::Validator { + bail!("Custom path set for `--storage`, but `--node-data` is not set."); + } else { + warn!("Custom path set for `--storage`, but `--node-data` is not set. The latter will use the default path."); + } } // Parse the node data directory. From 3e2c045fb8bb9c58625db2b45c3ad45c8be7ec5d Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 8 Jan 2026 18:47:46 -0800 Subject: [PATCH 11/13] refactor(cli): rename node-data to node-data-storage and storage to ledger-storage --- .gitignore | 8 +++++--- README.md | 11 +++++++---- cli/src/commands/start.rs | 38 ++++++++++++++++++++------------------ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index c96ff6a4e6..48115569e7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,19 +4,21 @@ wasm/Cargo.lock **/build **.history-* -**.logs-* validator-* **proptest-regressions/ **package-lock.json **node_modules/ -jwt_secret*.txt # Files created by a running snarkOS node **.ledger-* -**node-data-* +**.node-data-* + +# Files created by the devnet script +**.logs-* # Legacy files created by a running snarkOS node **.current-proposal-cache-* **cached_gateway_peers **cached_router_peers **.bft-storage-*/ +**jwt_secret*.txt \ No newline at end of file diff --git a/README.md b/README.md index a04cf60216..dcde4feee4 100644 --- a/README.md +++ b/README.md @@ -406,11 +406,14 @@ The following are the options for the `snarkos start` command: --metrics-ip Specify the IP address and port for the metrics exporter - --storage - Specify the path to a directory containing the storage database for the ledger. This flag overrides the default path, even when `--dev` is set + --ledger-storage + Specify the directory that holds all ledger data, e.g., blocks and transactions. + This flag overrides the default path, even when `--dev` is set. + + The old name for this flag (`--storage`) is DEPRECATED and will eventually be removed. - --node-data - Set a custom path for the node-specific data. + --node-data-storage + Specify the directory that holds node-specific data, that is not part of the global ledger. This flag overrides the default path, even when `--dev` is set. That folder may contain sensitive data, such as the JWT secret, and should not be shared with untrusted parties. diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 89fd68182a..74369f235e 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -237,18 +237,20 @@ pub struct Start { #[clap(long, requires = "metrics")] pub metrics_ip: Option, - /// Specify the path to a directory containing the storage database for the ledger. + /// Specify the directory that holds all ledger data, e.g., blocks and transactions. /// This flag overrides the default path, even when `--dev` is set. - #[clap(long)] - pub storage: Option, + /// + /// The old name for this flag (`--storage`) is DEPRECATED and will eventually be removed. + #[clap(long, verbatim_doc_comment, alias = "storage")] + pub ledger_storage: Option, - /// Set a custom path for the node-specific data. + /// Specify the directory that holds node-specific data, that is not part of the global ledger. /// This flag overrides the default path, even when `--dev` is set. /// /// That folder may contain sensitive data, such as the JWT secret, and should not be shared with untrusted parties. /// For validators, it also contains the latest proposal cache, which is required to participate in consensus. #[clap(long, verbatim_doc_comment)] - pub node_data: Option, + pub node_data_storage: Option, /// Enables the node to prefetch initial blocks from a CDN #[clap(long, conflicts_with = "nocdn")] @@ -711,7 +713,7 @@ impl Start { }; // Initialize the storage mode. - let storage_mode = match &self.storage { + let storage_mode = match &self.ledger_storage { Some(path) => StorageMode::Custom(path.clone()), None => match self.dev { Some(id) => StorageMode::Development(id), @@ -722,33 +724,33 @@ impl Start { // Users may have unintentionally set a custom path for the ledger, but not for the node data. // For validators, we make this an errors, so important files like the proposal cache are stored at the location // exepcted by the node operator. - if self.node_data.is_some() && !matches!(storage_mode, StorageMode::Custom(_)) { + if self.node_data_storage.is_some() && !matches!(storage_mode, StorageMode::Custom(_)) { if node_type == NodeType::Validator { - bail!("Custom path set for `--storage`, but not for `--node-data`.") + bail!("Custom path set for `--node-data-storage`, but not for `--ledger-storage`.") } else { - warn!("Custom path set for `--storage`, but not for `--node-data`. The latter will use the default path."); + warn!("Custom path set for `--node-data-storage`, but not for `--ledger-storage`. The latter will use the default path."); } - } else if matches!(storage_mode, StorageMode::Custom(_)) && self.node_data.is_none() { + } else if matches!(storage_mode, StorageMode::Custom(_)) && self.node_data_storage.is_none() { if node_type == NodeType::Validator { - bail!("Custom path set for `--storage`, but `--node-data` is not set."); + bail!("Custom path set for `--ledger-storage`, but not for `--node-data-storage`."); } else { - warn!("Custom path set for `--storage`, but `--node-data` is not set. The latter will use the default path."); + warn!("Custom path set for `--ledger-storage`, but not for `--node-data-storage`. The latter will use the default path."); } } // Parse the node data directory. - let node_data_dir = parse_node_data_dir(&self.node_data, N::ID, self.dev).with_context(|| "Failed to setup node configuration directory")?; + let node_data_dir = parse_node_data_dir(&self.node_data_storage, N::ID, self.dev).with_context(|| "Failed to setup node configuration directory")?; // Make sure the directory exists before continue. let data_path = node_data_dir.path(); if !data_path.exists() { - info!("Creating node data directory at {data_path:?}"); + info!("Creating directore for node data storage at {data_path:?}"); std::fs::create_dir_all(data_path) - .with_context(|| format!("Failed to create node data directory at {data_path:?}"))? + .with_context(|| format!("Failed to create directory for node data storage at {data_path:?}"))? } else if !data_path.is_dir() { - bail!("Node data directory at {data_path:?} is not a directory"); + bail!("Node data storage location at {data_path:?} is not a directory"); } else { - debug!("Using existing node data directory at {data_path:?}"); + debug!("Using existing directory at {data_path:?} for node data storage"); } // Checks for the old storage format and prints instructions to migrate. @@ -836,7 +838,7 @@ impl Start { // Initialize the node. let node = match node_type { - NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, dev_txs, self.dev, signal_handler.clone()).await, + NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, dev_txs, self.dev, signal_handler.clone()).await, NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, node_data_dir, self.trusted_peers_only, self.dev, signal_handler.clone()).await, NodeType::Client => Node::new_client(node_ip, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, self.dev, signal_handler.clone()).await, NodeType::BootstrapClient => Node::new_bootstrap_client(node_ip, account, *genesis.header(), self.dev).await, From e08e48391503081b61e7669d82ad4d9125a2e53f Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 8 Jan 2026 18:50:54 -0800 Subject: [PATCH 12/13] misc(cli): minor documentation fixes --- cli/src/helpers/args.rs | 4 ++-- node/bft/src/helpers/proposal_cache.rs | 4 ++-- utilities/src/node_data.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/src/helpers/args.rs b/cli/src/helpers/args.rs index 7d8d931660..ab6ba9e55d 100644 --- a/cli/src/helpers/args.rs +++ b/cli/src/helpers/args.rs @@ -79,11 +79,11 @@ pub(crate) fn parse_private_key( /// Returns the path to the node configuration directory. pub(crate) fn parse_node_data_dir( - node_data_dir: &Option, + custom_node_data_dir: &Option, network: u16, dev: Option, ) -> Result { - if let Some(custom) = node_data_dir { + if let Some(custom) = custom_node_data_dir { return Ok(NodeDataDir::new(custom.clone())); } diff --git a/node/bft/src/helpers/proposal_cache.rs b/node/bft/src/helpers/proposal_cache.rs index 419d37fc2f..faebadb8d9 100644 --- a/node/bft/src/helpers/proposal_cache.rs +++ b/node/bft/src/helpers/proposal_cache.rs @@ -65,14 +65,14 @@ impl ProposalCache { && self.signed_proposals.is_valid(expected_signer) } - /// Returns `true` if a proposal cache given network and `dev`. + /// Returns `true` if a proposal cache exists for the given network and `dev`. pub fn exists(node_data_dir: &NodeDataDir) -> bool { proposal_cache_path(node_data_dir).exists() } /// Load the proposal cache from the file system and ensure that the proposal cache is valid. pub fn load(expected_signer: Address, node_data_dir: &NodeDataDir) -> Result { - // Construct the proposal cache file system pa + // Construct the proposal cache file system path. let path = proposal_cache_path(node_data_dir); // Deserialize the proposal cache from the file system. diff --git a/utilities/src/node_data.rs b/utilities/src/node_data.rs index ec8bce93a8..1dda4d5e28 100644 --- a/utilities/src/node_data.rs +++ b/utilities/src/node_data.rs @@ -30,7 +30,7 @@ pub const CURRENT_PROPOSAL_CACHE_FILE: &str = "current-proposal-cache"; /// The filename of the validator whitelist. pub const VALIDATOR_WHITELIST_FILE: &str = "validator-whitelist"; -/// The old filename of the JWT secret for a given address. +/// The filename of the JWT secret for a given address. pub fn jwt_secret_file(address: &D) -> PathBuf { PathBuf::from(format!("jwt_secret_{address}.txt")) } From c067ad504105881b326a0cffafa9902429497198 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 8 Jan 2026 18:54:37 -0800 Subject: [PATCH 13/13] misc(cli): use jwt_secret_file in jwt_secret_path --- cli/src/commands/start.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 74369f235e..17973b58ec 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -27,7 +27,7 @@ use snarkos_node::{ rest::DEFAULT_REST_PORT, router::DEFAULT_NODE_PORT, }; -use snarkos_utilities::{NodeDataDir, SignalHandler, node_data}; +use snarkos_utilities::{NodeDataDir, SignalHandler, jwt_secret_file, node_data}; use snarkvm::{ console::{ @@ -508,10 +508,8 @@ impl Start { } /// Returns the path to where the JWT secret for the node is stored. - fn jwt_secret_path(node_data_dir: &NodeDataDir, address: &Address) -> Result { - let mut path = node_data_dir.path().to_path_buf(); - path.push(format!("jwt_secret_{address}.txt")); - Ok(path) + fn jwt_secret_path(node_data_dir: &NodeDataDir, address: &Address) -> PathBuf { + node_data_dir.path().join(jwt_secret_file(address)) } /// Returns an alternative genesis block if the node is in development mode. @@ -769,7 +767,7 @@ impl Start { // Create the JWT token based on the given secret. let jwt_token = snarkos_node_rest::Claims::new(account.address(), Some(jwt_bytes), self.jwt_timestamp).to_jwt_string()?; // Store the JWT secret to a file. - let path = Self::jwt_secret_path(&node_data_dir, &account.address())?; + let path = Self::jwt_secret_path(&node_data_dir, &account.address()); std::fs::write(path, &jwt_token)?; // Return the JWT token for optional printing. Some(jwt_token) @@ -777,7 +775,7 @@ impl Start { // Create a random JWT token. let jwt_token = snarkos_node_rest::Claims::new(account.address(), None, self.jwt_timestamp).to_jwt_string()?; // Store the JWT secret to a file. - let path = Self::jwt_secret_path(&node_data_dir, &account.address())?; + let path = Self::jwt_secret_path(&node_data_dir, &account.address()); std::fs::write(path, &jwt_token)? ; // Return the JWT token for optional printing. Some(jwt_token)