From c6cc85b2df91641d19b67242f95417adda013f2b Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:57:32 +0100 Subject: [PATCH 1/6] test(config): mainnet gate tests for Movement - admin_service and inspection_service reject insecure config on mainnet; the Movement (126) cases fail today since is_mainnet() matches only Aptos chain id 1 --- config/src/config/admin_service_config.rs | 44 +++++++++++++++++++ .../src/config/inspection_service_config.rs | 44 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/config/src/config/admin_service_config.rs b/config/src/config/admin_service_config.rs index e4096b108c0..344f521f330 100644 --- a/config/src/config/admin_service_config.rs +++ b/config/src/config/admin_service_config.rs @@ -228,4 +228,48 @@ mod tests { assert_eq!(node_config.admin_service.enabled, Some(false)); assert!(modified_config); } + + // Mainnet must reject an admin service with empty auth. Passes for Aptos + // mainnet (1); fails for Movement mainnet (126) until is_mainnet() matches 126. + fn admin_service_enabled_without_auth() -> NodeConfig { + NodeConfig { + admin_service: AdminServiceConfig { + enabled: Some(true), + ..Default::default() + }, + ..Default::default() + } + } + + #[test] + fn admin_service_requires_auth_on_aptos_mainnet() { + AdminServiceConfig::sanitize( + &admin_service_enabled_without_auth(), + NodeType::Validator, + Some(ChainId::new(1)), // Aptos mainnet + ) + .expect_err("mainnet must reject admin service with empty auth"); + } + + #[test] + fn admin_service_requires_auth_on_movement_mainnet() { + AdminServiceConfig::sanitize( + &admin_service_enabled_without_auth(), + NodeType::Validator, + Some(ChainId::new(126)), + ) + .expect_err("mainnet must reject admin service with empty auth"); + } + + // Same as the new(1) case but via the ChainId::mainnet() helper — cross-checks + // that the number behind the `mainnet` name still resolves to a production chain. + #[test] + fn admin_service_requires_auth_on_aptos_mainnet_via_helper() { + AdminServiceConfig::sanitize( + &admin_service_enabled_without_auth(), + NodeType::Validator, + Some(ChainId::mainnet()), + ) + .expect_err("mainnet must reject admin service with empty auth"); + } } diff --git a/config/src/config/inspection_service_config.rs b/config/src/config/inspection_service_config.rs index ea510d5b4ca..b46a155aeda 100644 --- a/config/src/config/inspection_service_config.rs +++ b/config/src/config/inspection_service_config.rs @@ -241,4 +241,48 @@ mod tests { .unwrap_err(); assert!(matches!(error, Error::ConfigSanitizerFailed(_, _))); } + + // Mainnet validators must not expose configuration. Passes for Aptos mainnet + // (1); fails for Movement mainnet (126) until is_mainnet() matches 126. + fn validator_exposing_configuration() -> NodeConfig { + NodeConfig { + inspection_service: InspectionServiceConfig { + expose_configuration: true, + ..Default::default() + }, + ..Default::default() + } + } + + #[test] + fn inspection_rejects_config_exposure_on_aptos_mainnet() { + InspectionServiceConfig::sanitize( + &validator_exposing_configuration(), + NodeType::Validator, + Some(ChainId::new(1)), // Aptos mainnet + ) + .expect_err("mainnet validator must not expose configuration"); + } + + #[test] + fn inspection_rejects_config_exposure_on_movement_mainnet() { + InspectionServiceConfig::sanitize( + &validator_exposing_configuration(), + NodeType::Validator, + Some(ChainId::new(126)), + ) + .expect_err("mainnet validator must not expose configuration"); + } + + // Same as the new(1) case but via the ChainId::mainnet() helper — cross-checks + // that the number behind the `mainnet` name still resolves to a production chain. + #[test] + fn inspection_rejects_config_exposure_on_aptos_mainnet_via_helper() { + InspectionServiceConfig::sanitize( + &validator_exposing_configuration(), + NodeType::Validator, + Some(ChainId::mainnet()), + ) + .expect_err("mainnet validator must not expose configuration"); + } } From 0bb890e21886da9f69f5778e408fe8b89e35cc9f Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:47:29 +0100 Subject: [PATCH 2/6] fix(chain-id): point mainnet/testnet at Movement - is_mainnet()/is_testnet() and ChainId::mainnet()/testnet() now resolve to Movement chain IDs (126/250); NamedChain discriminants unchanged - remove unused is_movement_mainnet()/is_movement_testnet() (zero call sites; their only caller was refactored out previously) - drop hardcoded Aptos genesis-waypoint and seed-peer injection from node config optimization - update timed-features test to exercise Movement chains --- config/src/config/config_optimizer.rs | 220 +------------------- config/src/config/execution_config.rs | 116 +---------- types/src/chain_id.rs | 18 +- types/src/on_chain_config/timed_features.rs | 110 +++------- 4 files changed, 55 insertions(+), 409 deletions(-) diff --git a/config/src/config/config_optimizer.rs b/config/src/config/config_optimizer.rs index 0165cbadb97..5c47fc7a694 100644 --- a/config/src/config/config_optimizer.rs +++ b/config/src/config/config_optimizer.rs @@ -9,15 +9,12 @@ use crate::{ config::{ node_config_loader::NodeType, utils::get_config_name, AdminServiceConfig, Error, ExecutionConfig, IndexerConfig, InspectionServiceConfig, LoggerConfig, MempoolConfig, - NodeConfig, Peer, PeerRole, PeerSet, StateSyncConfig, + NodeConfig, StateSyncConfig, }, network_id::NetworkId, }; -use aptos_crypto::{x25519, ValidCryptoMaterialStringExt}; -use aptos_types::{chain_id::ChainId, network_address::NetworkAddress, PeerId}; -use maplit::hashset; +use aptos_types::chain_id::ChainId; use serde_yaml::Value; -use std::{collections::HashMap, str::FromStr}; // Useful optimizer constants const OPTIMIZER_STRING: &str = "Optimizer"; @@ -27,39 +24,6 @@ const VALIDATOR_NETWORK_OPTIMIZER_NAME: &str = "ValidatorNetworkConfigOptimizer" const IDENTITY_KEY_FILE: &str = "ephemeral_identity_key"; -// Mainnet seed peers. Each seed peer entry is a tuple -// of (account address, public key, network address). -const MAINNET_SEED_PEERS: [(&str, &str, &str); 1] = [( - "568fdb6acf26aae2a84419108ff13baa3ebf133844ef18e23a9f47b5af16b698", - "0x003cc2ed36e7d486539ac2c411b48d962f1ef17d884c3a7109cad43f16bd5008", - "/dns/node1.cloud-b.mainnet.aptoslabs.com/tcp/6182/noise-ik/0x003cc2ed36e7d486539ac2c411b48d962f1ef17d884c3a7109cad43f16bd5008/handshake/0", -)]; - -// Testnet seed peers. Each seed peer entry is a tuple -// of (account address, public key, network address). -const TESTNET_SEED_PEERS: [(&str, &str, &str); 4] = [ - ( - "31e55012a7d439dcd16fee0509cd5855c1fbdc62057ba7fac3f7c88f5453dd8e", - "0x87bb19b02580b7e2a91a8e9342ec77ffd8f3ad967f54e77b22aaf558c5c11755", - "/dns/seed0.testnet.aptoslabs.com/tcp/6182/noise-ik/0x87bb19b02580b7e2a91a8e9342ec77ffd8f3ad967f54e77b22aaf558c5c11755/handshake/0", - ), - ( - "116176e2af223a8b7f8db80dc52f7a423b4d7f8c0553a1747e92ef58849aff4f", - "0xc2f24389f31c9c18d2ceb69d153ad9299e0ea7bbd66f457e0a28ef41c77c2b64", - "/dns/seed1.testnet.aptoslabs.com/tcp/6182/noise-ik/0xc2f24389f31c9c18d2ceb69d153ad9299e0ea7bbd66f457e0a28ef41c77c2b64/handshake/0", - ), - ( - "12000330d7cd8a748f46c25e6ce5d236a27e13d0b510d4516ac84ecc5fddd002", - "0x171c661e5b785283978a74eafc52a906e68c73ae78119737b92f93507c753933", - "/dns/seed2.testnet.aptoslabs.com/tcp/6182/noise-ik/0x171c661e5b785283978a74eafc52a906e68c73ae78119737b92f93507c753933/handshake/0", - ), - ( - "03c04549114877c55f45649aba48ac0a4ff086ab7bdce3b8cc8d3d9947bc0d99", - "0xafc38bf177bd825326a1c314748612137d2b35dae6472932806806a32c23174a", - "/dns/seed3.testnet.aptoslabs.com/tcp/6182/noise-ik/0xafc38bf177bd825326a1c314748612137d2b35dae6472932806806a32c23174a/handshake/0", - ), -]; - /// A trait for optimizing node configs (and their sub-configs) by tweaking /// config values based on node types, chain IDs and compiler features. /// @@ -180,37 +144,20 @@ fn optimize_all_network_configs( /// Optimize the public network config according to the node type and chain ID fn optimize_public_network_config( node_config: &mut NodeConfig, - local_config_yaml: &Value, + _local_config_yaml: &Value, node_type: NodeType, - chain_id: Option, + _chain_id: Option, ) -> Result { // We only need to optimize the public network config for VFNs and PFNs if node_type.is_validator() { return Ok(false); } - // Add seeds to the public network config let mut modified_config = false; - for (index, fullnode_network_config) in node_config.full_node_networks.iter_mut().enumerate() { - let local_network_config_yaml = &local_config_yaml["full_node_networks"][index]; - - // Optimize the public network configs + for fullnode_network_config in node_config.full_node_networks.iter_mut() { + // No automatic seed injection. Public fullnodes configure their seeds + // via node config. if fullnode_network_config.network_id == NetworkId::Public { - // Only add seeds to testnet and mainnet (as they are long living networks) - if local_network_config_yaml["seeds"].is_null() { - if let Some(chain_id) = chain_id { - if chain_id.is_testnet() { - fullnode_network_config.seeds = - create_seed_peers(TESTNET_SEED_PEERS.into())?; - modified_config = true; - } else if chain_id.is_mainnet() { - fullnode_network_config.seeds = - create_seed_peers(MAINNET_SEED_PEERS.into())?; - modified_config = true; - } - } - } - // If the identity key was not set in the config, attempt to // load it from disk. Otherwise, save the already generated // one to disk (for future runs). @@ -261,61 +208,6 @@ fn optimize_validator_network_config( Ok(modified_config) } -/// Creates and returns a set of seed peers from the given entries -fn create_seed_peers(seed_peer_entries: Vec<(&str, &str, &str)>) -> Result { - // Create a map of seed peers - let mut seed_peers = HashMap::new(); - - // Add the seed peers - for (account_address, public_key, network_address) in seed_peer_entries { - let (peer_address, peer) = build_seed_peer(account_address, public_key, network_address)?; - seed_peers.insert(peer_address, peer); - } - - Ok(seed_peers) -} - -/// Builds a seed peer using the specified peer information -fn build_seed_peer( - account_address_hex: &str, - public_key_hex: &str, - network_address_str: &str, -) -> Result<(PeerId, Peer), Error> { - // Parse the account address - let account_address = PeerId::from_hex(account_address_hex).map_err(|error| { - Error::Unexpected(format!( - "Failed to parse peer account address: {:?}. Error: {:?}", - account_address_hex, error - )) - })?; - - // Parse the x25519 public key - let public_key = x25519::PublicKey::from_encoded_string(public_key_hex).map_err(|error| { - Error::Unexpected(format!( - "Failed to parse peer public key: {:?}. Error: {:?}", - public_key_hex, error - )) - })?; - - // Parse the network address string - let network_address = NetworkAddress::from_str(network_address_str).map_err(|error| { - Error::Unexpected(format!( - "Failed to parse peer network address: {:?}. Error: {:?}", - network_address_str, error - )) - })?; - - // Build the peer struct - let peer = Peer { - addresses: vec![network_address], - keys: hashset! {public_key}, - role: PeerRole::Upstream, - }; - - // Return the account address and peer - Ok((account_address, peer)) -} - #[cfg(test)] mod tests { use super::*; @@ -325,10 +217,10 @@ mod tests { }, network_id::NetworkId, }; - use aptos_crypto::{Uniform, ValidCryptoMaterial}; - use aptos_types::{account_address::AccountAddress, waypoint::Waypoint}; + use aptos_crypto::{x25519, Uniform, ValidCryptoMaterial}; + use aptos_types::waypoint::Waypoint; use rand::rngs::OsRng; - use std::{io::Write, path::PathBuf}; + use std::{collections::HashMap, io::Write, path::PathBuf}; use tempfile::{tempdir, NamedTempFile}; fn setup_storage_config_with_temp_dir() -> (StorageConfig, PathBuf) { @@ -376,98 +268,6 @@ mod tests { assert!(!modified_config); } - #[test] - fn test_optimize_public_network_config_mainnet() { - // Create a public network config with no seeds - let mut node_config = NodeConfig { - storage: setup_storage_config_with_temp_dir().0, - full_node_networks: vec![NetworkConfig { - network_id: NetworkId::Public, - seeds: HashMap::new(), - ..Default::default() - }], - ..Default::default() - }; - - // Optimize the public network config and verify modifications are made - let modified_config = optimize_public_network_config( - &mut node_config, - &serde_yaml::from_str("{}").unwrap(), // An empty local config - NodeType::ValidatorFullnode, - Some(ChainId::mainnet()), - ) - .unwrap(); - assert!(modified_config); - - // Verify that the mainnet seed peers have been added to the config - let public_network_config = &node_config.full_node_networks[0]; - let public_seeds = &public_network_config.seeds; - assert_eq!(public_seeds.len(), MAINNET_SEED_PEERS.len()); - - // Verify that the seed peers contain the expected values - for (account_address, public_key, network_address) in MAINNET_SEED_PEERS { - // Fetch the seed peer - let seed_peer = public_seeds - .get(&AccountAddress::from_hex(account_address).unwrap()) - .unwrap(); - - // Verify the seed peer properties - assert_eq!(seed_peer.role, PeerRole::Upstream); - assert!(seed_peer - .addresses - .contains(&NetworkAddress::from_str(network_address).unwrap())); - assert!(seed_peer - .keys - .contains(&x25519::PublicKey::from_encoded_string(public_key).unwrap())); - } - } - - #[test] - fn test_optimize_public_network_config_testnet() { - // Create a public network config with no seeds - let mut node_config = NodeConfig { - storage: setup_storage_config_with_temp_dir().0, - full_node_networks: vec![NetworkConfig { - network_id: NetworkId::Public, - seeds: HashMap::new(), - ..Default::default() - }], - ..Default::default() - }; - - // Optimize the public network config and verify modifications are made - let modified_config = optimize_public_network_config( - &mut node_config, - &serde_yaml::from_str("{}").unwrap(), // An empty local config - NodeType::PublicFullnode, - Some(ChainId::testnet()), - ) - .unwrap(); - assert!(modified_config); - - // Verify that the testnet seed peers have been added to the config - let public_network_config = &node_config.full_node_networks[0]; - let public_seeds = &public_network_config.seeds; - assert_eq!(public_seeds.len(), TESTNET_SEED_PEERS.len()); - - // Verify that the seed peers contain the expected values - for (account_address, public_key, network_address) in TESTNET_SEED_PEERS { - // Fetch the seed peer - let seed_peer = public_seeds - .get(&AccountAddress::from_hex(account_address).unwrap()) - .unwrap(); - - // Verify the seed peer properties - assert_eq!(seed_peer.role, PeerRole::Upstream); - assert!(seed_peer - .addresses - .contains(&NetworkAddress::from_str(network_address).unwrap())); - assert!(seed_peer - .keys - .contains(&x25519::PublicKey::from_encoded_string(public_key).unwrap())); - } - } - #[test] fn test_optimize_public_network_config_no_override() { // Create a public network config diff --git a/config/src/config/execution_config.rs b/config/src/config/execution_config.rs index 0959ba8aa9d..d1fd8c68033 100644 --- a/config/src/config/execution_config.rs +++ b/config/src/config/execution_config.rs @@ -8,14 +8,13 @@ use crate::config::{ node_config_loader::NodeType, transaction_filter_type::Filter, utils::RootPath, Error, NodeConfig, }; -use aptos_types::{chain_id::ChainId, transaction::Transaction, waypoint::Waypoint}; +use aptos_types::{chain_id::ChainId, transaction::Transaction}; use serde::{Deserialize, Serialize}; use serde_yaml::Value; use std::{ fs::File, io::{Read, Write}, path::PathBuf, - str::FromStr, }; // Default execution concurrency level @@ -23,11 +22,6 @@ pub const DEFAULT_EXECUTION_CONCURRENCY_LEVEL: u16 = 32; // Genesis constants const GENESIS_BLOB_FILENAME: &str = "genesis.blob"; -const GENESIS_VERSION: u64 = 0; -const MAINNET_GENESIS_WAYPOINT: &str = - "0:6072b68a942aace147e0655c5704beaa255c84a7829baa4e72a500f1516584c4"; -const TESTNET_GENESIS_WAYPOINT: &str = - "0:4b56f15c1dcef7f9f3eb4b4798c0cba0f1caacc0d35f1c80ad9b7a21f1f8b454"; #[derive(Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(default, deny_unknown_fields)] @@ -187,46 +181,9 @@ impl ConfigOptimizer for ExecutionConfig { _node_type: NodeType, chain_id: Option, ) -> Result { - let execution_config = &mut node_config.execution; - let local_execution_config_yaml = &local_config_yaml["execution"]; - - // If the base config has a non-genesis waypoint, we should automatically - // inject the genesis waypoint into the execution config (if it doesn't exist). - // We do this for testnet and mainnet only (as they are long lived networks). - if node_config.base.waypoint.waypoint().version() != GENESIS_VERSION - && execution_config.genesis_waypoint.is_none() - && local_execution_config_yaml["genesis_waypoint"].is_null() - { - // Determine the genesis waypoint string to use - let genesis_waypoint_str = match chain_id { - Some(chain_id) => { - if chain_id.is_mainnet() { - MAINNET_GENESIS_WAYPOINT - } else if chain_id.is_testnet() { - TESTNET_GENESIS_WAYPOINT - } else { - return Ok(false); // Return early (this is not testnet or mainnet) - } - }, - None => return Ok(false), // Return early (no chain ID was specified!) - }; - - // Construct a genesis waypoint from the string - let genesis_waypoint = match Waypoint::from_str(genesis_waypoint_str) { - Ok(waypoint) => waypoint, - Err(error) => panic!( - "Invalid genesis waypoint string: {:?}. Error: {:?}", - genesis_waypoint_str, error - ), - }; - let genesis_waypoint_config = WaypointConfig::FromConfig(genesis_waypoint); - - // Inject the genesis waypoint into the execution config - execution_config.genesis_waypoint = Some(genesis_waypoint_config); - - return Ok(true); // The config was modified - } - + // No automatic genesis-waypoint injection. A genesis waypoint, if + // needed, is provided via node config (genesis_waypoint). + let _ = (node_config, local_config_yaml, chain_id); Ok(false) // The config was not modified } } @@ -237,9 +194,10 @@ mod test { use aptos_temppath::TempPath; use aptos_types::{ transaction::{ChangeSet, Transaction, WriteSetPayload}, + waypoint::Waypoint, write_set::WriteSetMut, }; - use std::{assert_eq, matches, vec}; + use std::{assert_eq, matches, str::FromStr, vec}; // Useful test constants const GENESIS_WAYPOINT: &str = @@ -270,68 +228,6 @@ mod test { assert!(!modified_config); } - #[test] - fn test_optimize_execution_config_non_genesis_mainnet() { - // Create a default node config - let mut node_config = NodeConfig::default(); - - // Verify the execution config does not have a genesis waypoint - assert!(&node_config.execution.genesis_waypoint.is_none()); - - // Inject a non-genesis waypoint into the base config - let non_genesis_waypoint = Waypoint::from_str(NON_GENESIS_WAYPOINT).unwrap(); - node_config.base.waypoint = WaypointConfig::FromConfig(non_genesis_waypoint); - - // Optimize the config for mainnet and verify modifications are made - let modified_config = ExecutionConfig::optimize( - &mut node_config, - &serde_yaml::from_str("{}").unwrap(), // An empty local config, - NodeType::Validator, - Some(ChainId::mainnet()), - ) - .unwrap(); - assert!(modified_config); - - // Verify that the mainnet genesis waypoint was injected into the execution config - let expected_genesis_waypoint = - WaypointConfig::FromConfig(Waypoint::from_str(MAINNET_GENESIS_WAYPOINT).unwrap()); - assert_eq!( - &node_config.execution.genesis_waypoint, - &Some(expected_genesis_waypoint) - ); - } - - #[test] - fn test_optimize_execution_config_non_genesis_testnet() { - // Create a default node config - let mut node_config = NodeConfig::default(); - - // Verify the execution config does not have a genesis waypoint - assert!(&node_config.execution.genesis_waypoint.is_none()); - - // Inject a non-genesis waypoint into the base config - let non_genesis_waypoint = Waypoint::from_str(NON_GENESIS_WAYPOINT).unwrap(); - node_config.base.waypoint = WaypointConfig::FromConfig(non_genesis_waypoint); - - // Optimize the config for testnet and verify modifications are made - let modified_config = ExecutionConfig::optimize( - &mut node_config, - &serde_yaml::from_str("{}").unwrap(), // An empty local config, - NodeType::PublicFullnode, - Some(ChainId::testnet()), - ) - .unwrap(); - assert!(modified_config); - - // Verify that the testnet genesis waypoint was injected into the execution config - let expected_genesis_waypoint = - WaypointConfig::FromConfig(Waypoint::from_str(TESTNET_GENESIS_WAYPOINT).unwrap()); - assert_eq!( - &node_config.execution.genesis_waypoint, - &Some(expected_genesis_waypoint) - ); - } - #[test] fn test_optimize_execution_config_skipped() { // Create a default node config diff --git a/types/src/chain_id.rs b/types/src/chain_id.rs index aa2c1707e14..b8913f15b09 100644 --- a/types/src/chain_id.rs +++ b/types/src/chain_id.rs @@ -83,24 +83,16 @@ impl FromStr for NamedChain { pub struct ChainId(u8); impl ChainId { - /// Returns true iff the chain ID matches testnet + /// Returns true iff the chain ID matches Movement testnet. pub fn is_testnet(&self) -> bool { - self.matches_named_chain(NamedChain::TESTNET) + self.matches_named_chain(NamedChain::MOVETESTNET) } - /// Returns true iff the chain ID matches mainnet + /// Returns true iff the chain ID matches Movement mainnet. pub fn is_mainnet(&self) -> bool { - self.matches_named_chain(NamedChain::MAINNET) - } - - pub fn is_movement_mainnet(&self) -> bool { self.matches_named_chain(NamedChain::MOVEMAINNET) } - pub fn is_movement_testnet(&self) -> bool { - self.matches_named_chain(NamedChain::MOVETESTNET) - } - /// Returns true iff the chain ID matches the given named chain fn matches_named_chain(&self, expected_chain: NamedChain) -> bool { if let Ok(named_chain) = NamedChain::from_chain_id(self) { @@ -211,11 +203,11 @@ impl ChainId { } pub fn testnet() -> Self { - ChainId::new(NamedChain::TESTNET.id()) + ChainId::new(NamedChain::MOVETESTNET.id()) } pub fn mainnet() -> Self { - ChainId::new(NamedChain::MAINNET.id()) + ChainId::new(NamedChain::MOVEMAINNET.id()) } } diff --git a/types/src/on_chain_config/timed_features.rs b/types/src/on_chain_config/timed_features.rs index fe5e895a1a0..f57f9d3d765 100644 --- a/types/src/on_chain_config/timed_features.rs +++ b/types/src/on_chain_config/timed_features.rs @@ -296,86 +296,44 @@ mod test { #[test] fn test_timed_features_activation() { use TimedFeatureFlag::*; - let jan_1_2024_micros = Utc - .with_ymd_and_hms(2024, 1, 1, 0, 0, 0) + + // On Movement's chains (mainnet 126 / testnet 250) every timed feature + // activates on 2025-08-11 (America/Los_Angeles), except + // UseFullTransactionSizeForTransactionMetadata, which activates 2026-05-04. + let before_micros = Utc + .with_ymd_and_hms(2025, 8, 1, 0, 0, 0) + .unwrap() + .timestamp_micros() as u64; + let after_micros = Utc + .with_ymd_and_hms(2025, 9, 1, 0, 0, 0) .unwrap() .timestamp_micros() as u64; - let nov_15_2024_micros = Utc - .with_ymd_and_hms(2024, 11, 15, 0, 0, 0) + let after_full_txn_size_micros = Utc + .with_ymd_and_hms(2026, 6, 1, 0, 0, 0) .unwrap() .timestamp_micros() as u64; - // Check testnet on Jan 1, 2024. - let testnet_jan_1_2024 = TimedFeaturesBuilder::new(ChainId::testnet(), jan_1_2024_micros); - assert!( - testnet_jan_1_2024.is_enabled(DisableInvariantViolationCheckInSwapLoc), - "DisableInvariantViolationCheckInSwapLoc should always be enabled" - ); - assert!( - testnet_jan_1_2024.is_enabled(_LimitTypeTagSize), - "LimitTypeTagSize should always be enabled" - ); - assert!( - !testnet_jan_1_2024.is_enabled(_ModuleComplexityCheck), - "ModuleComplexityCheck should be disabled on Jan 1, 2024 on testnet" - ); - assert!( - !testnet_jan_1_2024.is_enabled(EntryCompatibility), - "EntryCompatibility should be disabled on Jan 1, 2024 on testnet" - ); - // Check testnet on Nov 15, 2024. - let testnet_nov_15_2024 = TimedFeaturesBuilder::new(ChainId::testnet(), nov_15_2024_micros); - assert!( - testnet_nov_15_2024.is_enabled(DisableInvariantViolationCheckInSwapLoc), - "DisableInvariantViolationCheckInSwapLoc should always be enabled" - ); - assert!( - testnet_nov_15_2024.is_enabled(_LimitTypeTagSize), - "LimitTypeTagSize should always be enabled" - ); - assert!( - testnet_nov_15_2024.is_enabled(_ModuleComplexityCheck), - "ModuleComplexityCheck should be enabled on Nov 15, 2024 on testnet" - ); - assert!( - testnet_nov_15_2024.is_enabled(EntryCompatibility), - "EntryCompatibility should be enabled on Nov 15, 2024 on testnet" - ); - // Check mainnet on Jan 1, 2024. - let mainnet_jan_1_2024 = TimedFeaturesBuilder::new(ChainId::mainnet(), jan_1_2024_micros); - assert!( - mainnet_jan_1_2024.is_enabled(DisableInvariantViolationCheckInSwapLoc), - "DisableInvariantViolationCheckInSwapLoc should always be enabled" - ); - assert!( - mainnet_jan_1_2024.is_enabled(_LimitTypeTagSize), - "LimitTypeTagSize should always be enabled" - ); - assert!( - !mainnet_jan_1_2024.is_enabled(_ModuleComplexityCheck), - "ModuleComplexityCheck should be disabled on Jan 1, 2024 on mainnet" - ); - assert!( - !mainnet_jan_1_2024.is_enabled(EntryCompatibility), - "EntryCompatibility should be disabled on Jan 1, 2024 on mainnet" - ); - // Check mainnet on Nov 15, 2024. - let mainnet_nov_15_2024 = TimedFeaturesBuilder::new(ChainId::mainnet(), nov_15_2024_micros); - assert!( - mainnet_nov_15_2024.is_enabled(DisableInvariantViolationCheckInSwapLoc), - "DisableInvariantViolationCheckInSwapLoc should always be enabled" - ); - assert!( - mainnet_nov_15_2024.is_enabled(_LimitTypeTagSize), - "LimitTypeTagSize should always be enabled" - ); - assert!( - mainnet_nov_15_2024.is_enabled(_ModuleComplexityCheck), - "ModuleComplexityCheck should be enabled on Nov 15, 2024 on mainnet" - ); - assert!( - mainnet_nov_15_2024.is_enabled(EntryCompatibility), - "EntryCompatibility should be enabled on Nov 15, 2024 on mainnet" - ); + // Both our mainnet and testnet follow the same Movement schedule. + for chain_id in [ChainId::mainnet(), ChainId::testnet()] { + // Before the activation date, timed features are not yet enabled. + let before = TimedFeaturesBuilder::new(chain_id, before_micros); + assert!(!before.is_enabled(DisableInvariantViolationCheckInSwapLoc)); + assert!(!before.is_enabled(_LimitTypeTagSize)); + assert!(!before.is_enabled(_ModuleComplexityCheck)); + assert!(!before.is_enabled(EntryCompatibility)); + + // After the activation date, they are enabled, except the later + // UseFullTransactionSizeForTransactionMetadata. + let after = TimedFeaturesBuilder::new(chain_id, after_micros); + assert!(after.is_enabled(DisableInvariantViolationCheckInSwapLoc)); + assert!(after.is_enabled(_LimitTypeTagSize)); + assert!(after.is_enabled(_ModuleComplexityCheck)); + assert!(after.is_enabled(EntryCompatibility)); + assert!(!after.is_enabled(UseFullTransactionSizeForTransactionMetadata)); + + // After its own later activation date, it is enabled too. + let after_full = TimedFeaturesBuilder::new(chain_id, after_full_txn_size_micros); + assert!(after_full.is_enabled(UseFullTransactionSizeForTransactionMetadata)); + } } } From 9c46aa40a3b0af9dff0fe81ae970b55d275a4d4f Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:53:43 +0100 Subject: [PATCH 3/6] test(config): flip aptos-mainnet gate tests - assert the mainnet hardening gate does NOT fire on chain 1 (accept instead of reject), since is_mainnet() no longer matches chain 1 - rename to *_treats_aptos_mainnet_as_non_production --- config/src/config/admin_service_config.rs | 8 +++++--- config/src/config/inspection_service_config.rs | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/config/src/config/admin_service_config.rs b/config/src/config/admin_service_config.rs index 344f521f330..d42571756d4 100644 --- a/config/src/config/admin_service_config.rs +++ b/config/src/config/admin_service_config.rs @@ -241,14 +241,16 @@ mod tests { } } + // Chain 1 (Aptos mainnet) is not this network's mainnet, so the mainnet + // hardening gate does not fire and the empty auth list is accepted. #[test] - fn admin_service_requires_auth_on_aptos_mainnet() { + fn admin_service_treats_aptos_mainnet_as_non_production() { AdminServiceConfig::sanitize( &admin_service_enabled_without_auth(), NodeType::Validator, - Some(ChainId::new(1)), // Aptos mainnet + Some(ChainId::new(1)), // Aptos mainnet — not this network's mainnet ) - .expect_err("mainnet must reject admin service with empty auth"); + .expect("chain 1 is not this network's mainnet; gate does not fire"); } #[test] diff --git a/config/src/config/inspection_service_config.rs b/config/src/config/inspection_service_config.rs index b46a155aeda..f25ce61f7ee 100644 --- a/config/src/config/inspection_service_config.rs +++ b/config/src/config/inspection_service_config.rs @@ -254,14 +254,16 @@ mod tests { } } + // Chain 1 (Aptos mainnet) is not this network's mainnet, so the mainnet + // hardening gate does not fire and the exposed configuration is accepted. #[test] - fn inspection_rejects_config_exposure_on_aptos_mainnet() { + fn inspection_treats_aptos_mainnet_as_non_production() { InspectionServiceConfig::sanitize( &validator_exposing_configuration(), NodeType::Validator, - Some(ChainId::new(1)), // Aptos mainnet + Some(ChainId::new(1)), // Aptos mainnet — not this network's mainnet ) - .expect_err("mainnet validator must not expose configuration"); + .expect("chain 1 is not this network's mainnet; gate does not fire"); } #[test] From cd108b0646fdc2e574a3e8ef7b6ed3e7dd98b77a Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:26:15 +0100 Subject: [PATCH 4/6] fix(chain-id): drop Aptos NamedChain variants, fix mainnet gates - remove NamedChain::MAINNET (1) and TESTNET (2); IDs 1/2 stay valid numerically but the "mainnet"/"testnet" name strings no longer parse - accept "movement_mainnet"/"movement_testnet" in from_str so parsing round-trips with Display - drop the Aptos-specific timed-feature schedules keyed on the removed variants - point confidential_asset MAINNET_CHAIN_ID at Movement mainnet (126) so the allow-list gate engages --- .../confidential_asset.move | 4 +- crates/aptos-faucet/core/src/funder/common.rs | 7 +- crates/aptos/src/node/mod.rs | 8 +- types/src/chain_id.rs | 33 ++++---- types/src/on_chain_config/timed_features.rs | 84 +------------------ 5 files changed, 29 insertions(+), 107 deletions(-) diff --git a/aptos-move/framework/aptos-experimental/sources/confidential_asset/confidential_asset.move b/aptos-move/framework/aptos-experimental/sources/confidential_asset/confidential_asset.move index 32c07f8c04f..49ee659f265 100644 --- a/aptos-move/framework/aptos-experimental/sources/confidential_asset/confidential_asset.move +++ b/aptos-move/framework/aptos-experimental/sources/confidential_asset/confidential_asset.move @@ -89,8 +89,8 @@ module aptos_experimental::confidential_asset { /// The maximum number of transactions can be aggregated on the pending balance before rollover is required. const MAX_TRANSFERS_BEFORE_ROLLOVER: u64 = 65534; - /// The mainnet chain ID. If the chain ID is 1, the allow list is enabled. - const MAINNET_CHAIN_ID: u8 = 1; + /// The Movement mainnet chain ID. On mainnet the allow list is enabled. + const MAINNET_CHAIN_ID: u8 = 126; // // Structs diff --git a/crates/aptos-faucet/core/src/funder/common.rs b/crates/aptos-faucet/core/src/funder/common.rs index d88cf44e5f4..1c2ac3126a4 100644 --- a/crates/aptos-faucet/core/src/funder/common.rs +++ b/crates/aptos-faucet/core/src/funder/common.rs @@ -76,9 +76,10 @@ pub struct ApiConnectionConfig { #[clap(long, value_parser = ConfigKey::::from_encoded_string)] key: Option>, - /// Chain ID of the network this client is connecting to. For example, for mainnet: - /// "MAINNET" or 1, testnet: "TESTNET" or 2. If there is no predefined string - /// alias (e.g. "MAINNET"), just use the number. Note: Chain ID of 0 is not allowed. + /// Chain ID of the network this client is connecting to, as a number + /// (e.g. 126 for Movement mainnet, 250 for testnet). The aliases + /// "movement_mainnet" / "movement_testnet" are also accepted. + /// Note: Chain ID of 0 is not allowed. #[clap(long, default_value_t = ChainId::testnet())] pub chain_id: ChainId, } diff --git a/crates/aptos/src/node/mod.rs b/crates/aptos/src/node/mod.rs index e062e721cc9..e20a850bee7 100644 --- a/crates/aptos/src/node/mod.rs +++ b/crates/aptos/src/node/mod.rs @@ -1371,8 +1371,8 @@ pub struct CheckNetworkConnectivity { /// `ChainId` of remote server. /// Examples include: - /// - Chain numbers, e.g., `2`, `3` and `25`. - /// - Chain names, e.g., `devnet`, `testnet`, `mainnet` and `testing` (for local test networks). + /// - Chain numbers, e.g., `126`, `250` and `25`. + /// - Chain names, e.g., `movement_mainnet`, `movement_testnet`, `devnet` and `testing` (for local test networks). #[clap(long)] pub chain_id: ChainId, @@ -1508,7 +1508,7 @@ mod tests { "--address", "invalid-address", "--chain-id", - "mainnet", + "movement_mainnet", ]; let error_message = run_tool_with_args(args).await.unwrap_err(); assert_contains(error_message, "Invalid address"); @@ -1519,7 +1519,7 @@ mod tests { assert_contains(error_message, "invalid value"); // Verify that a failure to connect will return a timeout - let args = &["aptos", "node", "check-network-connectivity", "--address", "/ip4/31.71.116.169/tcp/0001/noise-ik/0x249f3301db104705652e0a0c471b46d13172b2baf14e31f007413f3baee46b0c/handshake/0", "--chain-id", "testnet"]; + let args = &["aptos", "node", "check-network-connectivity", "--address", "/ip4/31.71.116.169/tcp/0001/noise-ik/0x249f3301db104705652e0a0c471b46d13172b2baf14e31f007413f3baee46b0c/handshake/0", "--chain-id", "movement_testnet"]; let error_message = run_tool_with_args(args).await.unwrap_err(); assert_contains(error_message, "Timed out while checking endpoint"); } diff --git a/types/src/chain_id.rs b/types/src/chain_id.rs index b8913f15b09..54b58db93bf 100644 --- a/types/src/chain_id.rs +++ b/types/src/chain_id.rs @@ -8,17 +8,11 @@ use std::{convert::TryFrom, fmt, str::FromStr}; /// A registry of named chain IDs /// Its main purpose is to improve human readability of reserved chain IDs in config files and CLI /// When signing transactions for such chains, the numerical chain ID should still be used -/// (e.g. MAINNET has numeric chain ID 1, TESTNET has chain ID 2, etc) +/// (e.g. MOVEMAINNET has numeric chain ID 126, MOVETESTNET has chain ID 250) +/// Note: ChainId 0 is reserved to guard against accidental zero-initialization. #[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum NamedChain { - /// Users might accidentally initialize the ChainId field to 0, hence reserving ChainId 0 for accidental - /// initialization. - /// MAINNET is the Aptos mainnet production chain and is reserved for 1 - MAINNET = 1, - // Even though these CHAIN IDs do not correspond to MAINNET, changing them should be avoided since they - // can break test environments for various organisations. - TESTNET = 2, DEVNET = 3, TESTING = 4, PREMAINNET = 5, @@ -26,8 +20,6 @@ pub enum NamedChain { MOVETESTNET = 250, } -const MAINNET: &str = "mainnet"; -const TESTNET: &str = "testnet"; const DEVNET: &str = "devnet"; const TESTING: &str = "testing"; const PREMAINNET: &str = "premainnet"; @@ -47,8 +39,6 @@ impl NamedChain { pub fn from_chain_id(chain_id: &ChainId) -> Result { let chain_id = chain_id.id(); match chain_id { - 1 => Ok(NamedChain::MAINNET), - 2 => Ok(NamedChain::TESTNET), 3 => Ok(NamedChain::DEVNET), // TODO: this is not correct and should removed. The devnet chain ID changes. 4 => Ok(NamedChain::TESTING), 5 => Ok(NamedChain::PREMAINNET), @@ -64,8 +54,8 @@ impl FromStr for NamedChain { fn from_str(string: &str) -> Result { let named_chain = match string.to_lowercase().as_str() { - MAINNET => NamedChain::MAINNET, - TESTNET => NamedChain::TESTNET, + MOVEMENT_MAINNET => NamedChain::MOVEMAINNET, + MOVEMENT_TESTNET => NamedChain::MOVETESTNET, DEVNET => NamedChain::DEVNET, TESTING => NamedChain::TESTING, PREMAINNET => NamedChain::PREMAINNET, @@ -159,8 +149,6 @@ impl fmt::Display for NamedChain { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", match self { NamedChain::DEVNET => DEVNET, - NamedChain::TESTNET => TESTNET, - NamedChain::MAINNET => MAINNET, NamedChain::TESTING => TESTING, NamedChain::PREMAINNET => PREMAINNET, NamedChain::MOVEMAINNET => MOVEMENT_MAINNET, @@ -223,5 +211,18 @@ mod test { assert!(ChainId::from_str("255255").is_err()); assert_eq!(ChainId::from_str("TESTING").unwrap(), ChainId::test()); assert_eq!(ChainId::from_str("255").unwrap(), ChainId::new(255)); + + // The Movement chains parse from their canonical names, matching Display output. + assert_eq!( + ChainId::from_str("movement_mainnet").unwrap(), + ChainId::mainnet() + ); + assert_eq!( + ChainId::from_str("movement_testnet").unwrap(), + ChainId::testnet() + ); + // The Aptos "mainnet"/"testnet" names are no longer reserved. + assert!(ChainId::from_str("mainnet").is_err()); + assert!(ChainId::from_str("testnet").is_err()); } } diff --git a/types/src/on_chain_config/timed_features.rs b/types/src/on_chain_config/timed_features.rs index f57f9d3d765..8d06d7fb31e 100644 --- a/types/src/on_chain_config/timed_features.rs +++ b/types/src/on_chain_config/timed_features.rs @@ -89,61 +89,9 @@ impl TimedFeatureFlag { .with_ymd_and_hms(2025, 8, 11, 17, 0, 0) .unwrap() .with_timezone(&Utc), - // Enabled from the beginning of time. - (DisableInvariantViolationCheckInSwapLoc, TESTNET) => BEGINNING_OF_TIME, - (DisableInvariantViolationCheckInSwapLoc, MAINNET) => BEGINNING_OF_TIME, - - // Note: These have been enabled since the start due to a bug. - (_LimitTypeTagSize, TESTNET) => BEGINNING_OF_TIME, - (_LimitTypeTagSize, MAINNET) => BEGINNING_OF_TIME, - - (_ModuleComplexityCheck, TESTNET) => Los_Angeles - .with_ymd_and_hms(2024, 6, 25, 16, 0, 0) - .unwrap() - .with_timezone(&Utc), - (_ModuleComplexityCheck, MAINNET) => Los_Angeles - .with_ymd_and_hms(2024, 7, 3, 12, 0, 0) - .unwrap() - .with_timezone(&Utc), - - (EntryCompatibility, TESTNET) => Los_Angeles - .with_ymd_and_hms(2024, 11, 6, 12, 0, 0) - .unwrap() - .with_timezone(&Utc), - (EntryCompatibility, MAINNET) => Los_Angeles - .with_ymd_and_hms(2024, 11, 12, 12, 0, 0) - .unwrap() - .with_timezone(&Utc), - // Note: Activation time set to 1 hour after the beginning of time // so we can test the old and new behaviors in tests. (FixMemoryUsageTracking, TESTING) => Utc.with_ymd_and_hms(1970, 1, 1, 1, 0, 0).unwrap(), - (FixMemoryUsageTracking, TESTNET) => Los_Angeles - .with_ymd_and_hms(2025, 3, 7, 12, 0, 0) - .unwrap() - .with_timezone(&Utc), - (FixMemoryUsageTracking, MAINNET) => Los_Angeles - .with_ymd_and_hms(2025, 3, 11, 17, 0, 0) - .unwrap() - .with_timezone(&Utc), - - (ChargeBytesForPrints, TESTNET) => Los_Angeles - .with_ymd_and_hms(2025, 3, 7, 12, 0, 0) - .unwrap() - .with_timezone(&Utc), - (ChargeBytesForPrints, MAINNET) => Los_Angeles - .with_ymd_and_hms(2025, 3, 11, 17, 0, 0) - .unwrap() - .with_timezone(&Utc), - - (FixTableNativesMemoryDoubleCounting, TESTNET) => Los_Angeles - .with_ymd_and_hms(2025, 10, 16, 17, 0, 0) - .unwrap() - .with_timezone(&Utc), - (FixTableNativesMemoryDoubleCounting, MAINNET) => Los_Angeles - .with_ymd_and_hms(2025, 10, 21, 10, 0, 0) - .unwrap() - .with_timezone(&Utc), // 1 hour after the beginning of time (FixCryptoAlgebraNativesTypeTagConversion, _) => { @@ -153,8 +101,8 @@ impl TimedFeatureFlag { // Irrelevant for us except for testing (UseFullTransactionSizeForTransactionMetadata, _) => BEGINNING_OF_TIME, - // For chains other than testnet and mainnet, a timed feature is considered enabled from - // the very beginning, if left unspecified. + // For chains other than Movement mainnet and testnet, a timed feature is considered + // enabled from the very beginning, if left unspecified. (_, TESTING | DEVNET | PREMAINNET) => BEGINNING_OF_TIME, } } @@ -250,8 +198,6 @@ mod test { #[test] fn test_micros_conversion() { - use NamedChain::*; - assert_eq!( Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0) .unwrap() @@ -265,32 +211,6 @@ mod test { .timestamp_micros(), 1_731_628_800_000_000 ); - - assert_eq!( - TimedFeatureFlag::_ModuleComplexityCheck - .activation_time_on(&TESTNET) - .timestamp_micros(), - 1_719_356_400_000_000 - ); - assert_eq!( - TimedFeatureFlag::_ModuleComplexityCheck - .activation_time_on(&MAINNET) - .timestamp_micros(), - 1_720_033_200_000_000 - ); - - assert_eq!( - TimedFeatureFlag::EntryCompatibility - .activation_time_on(&TESTNET) - .timestamp_micros(), - 1_730_923_200_000_000 - ); - assert_eq!( - TimedFeatureFlag::EntryCompatibility - .activation_time_on(&MAINNET) - .timestamp_micros(), - 1_731_441_600_000_000 - ); } #[test] From 7810033d06c244fad112771854129a8c6cc0f482 Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:41:28 +0100 Subject: [PATCH 5/6] fix(config): inject Movement seed peers for public fullnodes - restore the public-network seed injection and point MAINNET/TESTNET_SEED_PEERS at Movement's mainnet/testnet consensus fullnodes - seeds are only added when the operator has not set any and the chain is Movement mainnet (126) or testnet (250) --- config/src/config/config_optimizer.rs | 203 ++++++++++++++++++++++++-- 1 file changed, 193 insertions(+), 10 deletions(-) diff --git a/config/src/config/config_optimizer.rs b/config/src/config/config_optimizer.rs index 5c47fc7a694..0846b00ea37 100644 --- a/config/src/config/config_optimizer.rs +++ b/config/src/config/config_optimizer.rs @@ -9,12 +9,15 @@ use crate::{ config::{ node_config_loader::NodeType, utils::get_config_name, AdminServiceConfig, Error, ExecutionConfig, IndexerConfig, InspectionServiceConfig, LoggerConfig, MempoolConfig, - NodeConfig, StateSyncConfig, + NodeConfig, Peer, PeerRole, PeerSet, StateSyncConfig, }, network_id::NetworkId, }; -use aptos_types::chain_id::ChainId; +use aptos_crypto::{x25519, ValidCryptoMaterialStringExt}; +use aptos_types::{chain_id::ChainId, network_address::NetworkAddress, PeerId}; +use maplit::hashset; use serde_yaml::Value; +use std::{collections::HashMap, str::FromStr}; // Useful optimizer constants const OPTIMIZER_STRING: &str = "Optimizer"; @@ -24,6 +27,22 @@ const VALIDATOR_NETWORK_OPTIMIZER_NAME: &str = "ValidatorNetworkConfigOptimizer" const IDENTITY_KEY_FILE: &str = "ephemeral_identity_key"; +// Movement mainnet seed peers. Each seed peer entry is a tuple +// of (account address, public key, network address). +const MAINNET_SEED_PEERS: [(&str, &str, &str); 1] = [( + "9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", + "0x9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", + "/dns/consensus.mainnet.movementnetwork.xyz/tcp/6182/noise-ik/9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F/handshake/0", +)]; + +// Movement testnet seed peers. Each seed peer entry is a tuple +// of (account address, public key, network address). +const TESTNET_SEED_PEERS: [(&str, &str, &str); 1] = [( + "9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", + "0x9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", + "/dns/consensus.testnet.movementnetwork.xyz/tcp/6182/noise-ik/9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F/handshake/0", +)]; + /// A trait for optimizing node configs (and their sub-configs) by tweaking /// config values based on node types, chain IDs and compiler features. /// @@ -144,20 +163,37 @@ fn optimize_all_network_configs( /// Optimize the public network config according to the node type and chain ID fn optimize_public_network_config( node_config: &mut NodeConfig, - _local_config_yaml: &Value, + local_config_yaml: &Value, node_type: NodeType, - _chain_id: Option, + chain_id: Option, ) -> Result { // We only need to optimize the public network config for VFNs and PFNs if node_type.is_validator() { return Ok(false); } + // Add seeds to the public network config let mut modified_config = false; - for fullnode_network_config in node_config.full_node_networks.iter_mut() { - // No automatic seed injection. Public fullnodes configure their seeds - // via node config. + for (index, fullnode_network_config) in node_config.full_node_networks.iter_mut().enumerate() { + let local_network_config_yaml = &local_config_yaml["full_node_networks"][index]; + + // Optimize the public network configs if fullnode_network_config.network_id == NetworkId::Public { + // Only add seeds to testnet and mainnet (as they are long living networks) + if local_network_config_yaml["seeds"].is_null() { + if let Some(chain_id) = chain_id { + if chain_id.is_testnet() { + fullnode_network_config.seeds = + create_seed_peers(TESTNET_SEED_PEERS.into())?; + modified_config = true; + } else if chain_id.is_mainnet() { + fullnode_network_config.seeds = + create_seed_peers(MAINNET_SEED_PEERS.into())?; + modified_config = true; + } + } + } + // If the identity key was not set in the config, attempt to // load it from disk. Otherwise, save the already generated // one to disk (for future runs). @@ -208,6 +244,61 @@ fn optimize_validator_network_config( Ok(modified_config) } +/// Creates and returns a set of seed peers from the given entries +fn create_seed_peers(seed_peer_entries: Vec<(&str, &str, &str)>) -> Result { + // Create a map of seed peers + let mut seed_peers = HashMap::new(); + + // Add the seed peers + for (account_address, public_key, network_address) in seed_peer_entries { + let (peer_address, peer) = build_seed_peer(account_address, public_key, network_address)?; + seed_peers.insert(peer_address, peer); + } + + Ok(seed_peers) +} + +/// Builds a seed peer using the specified peer information +fn build_seed_peer( + account_address_hex: &str, + public_key_hex: &str, + network_address_str: &str, +) -> Result<(PeerId, Peer), Error> { + // Parse the account address + let account_address = PeerId::from_hex(account_address_hex).map_err(|error| { + Error::Unexpected(format!( + "Failed to parse peer account address: {:?}. Error: {:?}", + account_address_hex, error + )) + })?; + + // Parse the x25519 public key + let public_key = x25519::PublicKey::from_encoded_string(public_key_hex).map_err(|error| { + Error::Unexpected(format!( + "Failed to parse peer public key: {:?}. Error: {:?}", + public_key_hex, error + )) + })?; + + // Parse the network address string + let network_address = NetworkAddress::from_str(network_address_str).map_err(|error| { + Error::Unexpected(format!( + "Failed to parse peer network address: {:?}. Error: {:?}", + network_address_str, error + )) + })?; + + // Build the peer struct + let peer = Peer { + addresses: vec![network_address], + keys: hashset! {public_key}, + role: PeerRole::Upstream, + }; + + // Return the account address and peer + Ok((account_address, peer)) +} + #[cfg(test)] mod tests { use super::*; @@ -217,10 +308,10 @@ mod tests { }, network_id::NetworkId, }; - use aptos_crypto::{x25519, Uniform, ValidCryptoMaterial}; - use aptos_types::waypoint::Waypoint; + use aptos_crypto::{Uniform, ValidCryptoMaterial}; + use aptos_types::{account_address::AccountAddress, waypoint::Waypoint}; use rand::rngs::OsRng; - use std::{collections::HashMap, io::Write, path::PathBuf}; + use std::{io::Write, path::PathBuf}; use tempfile::{tempdir, NamedTempFile}; fn setup_storage_config_with_temp_dir() -> (StorageConfig, PathBuf) { @@ -268,6 +359,98 @@ mod tests { assert!(!modified_config); } + #[test] + fn test_optimize_public_network_config_mainnet() { + // Create a public network config with no seeds + let mut node_config = NodeConfig { + storage: setup_storage_config_with_temp_dir().0, + full_node_networks: vec![NetworkConfig { + network_id: NetworkId::Public, + seeds: HashMap::new(), + ..Default::default() + }], + ..Default::default() + }; + + // Optimize the public network config and verify modifications are made + let modified_config = optimize_public_network_config( + &mut node_config, + &serde_yaml::from_str("{}").unwrap(), // An empty local config + NodeType::ValidatorFullnode, + Some(ChainId::mainnet()), + ) + .unwrap(); + assert!(modified_config); + + // Verify that the mainnet seed peers have been added to the config + let public_network_config = &node_config.full_node_networks[0]; + let public_seeds = &public_network_config.seeds; + assert_eq!(public_seeds.len(), MAINNET_SEED_PEERS.len()); + + // Verify that the seed peers contain the expected values + for (account_address, public_key, network_address) in MAINNET_SEED_PEERS { + // Fetch the seed peer + let seed_peer = public_seeds + .get(&AccountAddress::from_hex(account_address).unwrap()) + .unwrap(); + + // Verify the seed peer properties + assert_eq!(seed_peer.role, PeerRole::Upstream); + assert!(seed_peer + .addresses + .contains(&NetworkAddress::from_str(network_address).unwrap())); + assert!(seed_peer + .keys + .contains(&x25519::PublicKey::from_encoded_string(public_key).unwrap())); + } + } + + #[test] + fn test_optimize_public_network_config_testnet() { + // Create a public network config with no seeds + let mut node_config = NodeConfig { + storage: setup_storage_config_with_temp_dir().0, + full_node_networks: vec![NetworkConfig { + network_id: NetworkId::Public, + seeds: HashMap::new(), + ..Default::default() + }], + ..Default::default() + }; + + // Optimize the public network config and verify modifications are made + let modified_config = optimize_public_network_config( + &mut node_config, + &serde_yaml::from_str("{}").unwrap(), // An empty local config + NodeType::PublicFullnode, + Some(ChainId::testnet()), + ) + .unwrap(); + assert!(modified_config); + + // Verify that the testnet seed peers have been added to the config + let public_network_config = &node_config.full_node_networks[0]; + let public_seeds = &public_network_config.seeds; + assert_eq!(public_seeds.len(), TESTNET_SEED_PEERS.len()); + + // Verify that the seed peers contain the expected values + for (account_address, public_key, network_address) in TESTNET_SEED_PEERS { + // Fetch the seed peer + let seed_peer = public_seeds + .get(&AccountAddress::from_hex(account_address).unwrap()) + .unwrap(); + + // Verify the seed peer properties + assert_eq!(seed_peer.role, PeerRole::Upstream); + assert!(seed_peer + .addresses + .contains(&NetworkAddress::from_str(network_address).unwrap())); + assert!(seed_peer + .keys + .contains(&x25519::PublicKey::from_encoded_string(public_key).unwrap())); + } + } + #[test] fn test_optimize_public_network_config_no_override() { // Create a public network config From 63c0ea38deed9f991f9dd2a7305441aea1d53c41 Mon Sep 17 00:00:00 2001 From: apenzk <36140574+apenzk@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:54:23 +0100 Subject: [PATCH 6/6] fix(config): use Movement genesis waypoints for waypoint injection - restore the genesis-waypoint injection and point MAINNET/TESTNET_GENESIS_WAYPOINT at Movement's mainnet (126) and testnet (250) values, both at genesis version 0 - cite the movement-networks repo as the source for the genesis waypoints and the seed peers --- config/src/config/config_optimizer.rs | 10 ++- config/src/config/execution_config.rs | 118 ++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 10 deletions(-) diff --git a/config/src/config/config_optimizer.rs b/config/src/config/config_optimizer.rs index 0846b00ea37..e5a3f9b335c 100644 --- a/config/src/config/config_optimizer.rs +++ b/config/src/config/config_optimizer.rs @@ -27,16 +27,18 @@ const VALIDATOR_NETWORK_OPTIMIZER_NAME: &str = "ValidatorNetworkConfigOptimizer" const IDENTITY_KEY_FILE: &str = "ephemeral_identity_key"; -// Movement mainnet seed peers. Each seed peer entry is a tuple -// of (account address, public key, network address). +// Movement mainnet seed peers, published at +// https://github.com/movementlabsxyz/movement-networks (mainnet/configs/fullnode.yaml). +// Each seed peer entry is a tuple of (account address, public key, network address). const MAINNET_SEED_PEERS: [(&str, &str, &str); 1] = [( "9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", "0x9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", "/dns/consensus.mainnet.movementnetwork.xyz/tcp/6182/noise-ik/9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F/handshake/0", )]; -// Movement testnet seed peers. Each seed peer entry is a tuple -// of (account address, public key, network address). +// Movement testnet seed peers, published at +// https://github.com/movementlabsxyz/movement-networks (testnet/configs/fullnode.yaml). +// Each seed peer entry is a tuple of (account address, public key, network address). const TESTNET_SEED_PEERS: [(&str, &str, &str); 1] = [( "9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", "0x9967EBF40AC8C2CCB38709488952DA1826176584EA3067B63B1695362ECB3D1F", diff --git a/config/src/config/execution_config.rs b/config/src/config/execution_config.rs index d1fd8c68033..cd93e23ce6b 100644 --- a/config/src/config/execution_config.rs +++ b/config/src/config/execution_config.rs @@ -8,13 +8,14 @@ use crate::config::{ node_config_loader::NodeType, transaction_filter_type::Filter, utils::RootPath, Error, NodeConfig, }; -use aptos_types::{chain_id::ChainId, transaction::Transaction}; +use aptos_types::{chain_id::ChainId, transaction::Transaction, waypoint::Waypoint}; use serde::{Deserialize, Serialize}; use serde_yaml::Value; use std::{ fs::File, io::{Read, Write}, path::PathBuf, + str::FromStr, }; // Default execution concurrency level @@ -22,6 +23,13 @@ pub const DEFAULT_EXECUTION_CONCURRENCY_LEVEL: u16 = 32; // Genesis constants const GENESIS_BLOB_FILENAME: &str = "genesis.blob"; +const GENESIS_VERSION: u64 = 0; +// Movement genesis waypoints, published at +// https://github.com/movementlabsxyz/movement-networks (/genesis_waypoint.txt). +const MAINNET_GENESIS_WAYPOINT: &str = + "0:fb098815125443b6337c61b81aade770e7dcf69d93e8a636365a664939f100a3"; +const TESTNET_GENESIS_WAYPOINT: &str = + "0:1b6eeb5566c6267b610340e17d47849742ff488a21eeb3fc1971fb8c2b128133"; #[derive(Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(default, deny_unknown_fields)] @@ -181,9 +189,46 @@ impl ConfigOptimizer for ExecutionConfig { _node_type: NodeType, chain_id: Option, ) -> Result { - // No automatic genesis-waypoint injection. A genesis waypoint, if - // needed, is provided via node config (genesis_waypoint). - let _ = (node_config, local_config_yaml, chain_id); + let execution_config = &mut node_config.execution; + let local_execution_config_yaml = &local_config_yaml["execution"]; + + // If the base config has a non-genesis waypoint, we should automatically + // inject the genesis waypoint into the execution config (if it doesn't exist). + // We do this for testnet and mainnet only (as they are long lived networks). + if node_config.base.waypoint.waypoint().version() != GENESIS_VERSION + && execution_config.genesis_waypoint.is_none() + && local_execution_config_yaml["genesis_waypoint"].is_null() + { + // Determine the genesis waypoint string to use + let genesis_waypoint_str = match chain_id { + Some(chain_id) => { + if chain_id.is_mainnet() { + MAINNET_GENESIS_WAYPOINT + } else if chain_id.is_testnet() { + TESTNET_GENESIS_WAYPOINT + } else { + return Ok(false); // Return early (this is not testnet or mainnet) + } + }, + None => return Ok(false), // Return early (no chain ID was specified!) + }; + + // Construct a genesis waypoint from the string + let genesis_waypoint = match Waypoint::from_str(genesis_waypoint_str) { + Ok(waypoint) => waypoint, + Err(error) => panic!( + "Invalid genesis waypoint string: {:?}. Error: {:?}", + genesis_waypoint_str, error + ), + }; + let genesis_waypoint_config = WaypointConfig::FromConfig(genesis_waypoint); + + // Inject the genesis waypoint into the execution config + execution_config.genesis_waypoint = Some(genesis_waypoint_config); + + return Ok(true); // The config was modified + } + Ok(false) // The config was not modified } } @@ -194,10 +239,9 @@ mod test { use aptos_temppath::TempPath; use aptos_types::{ transaction::{ChangeSet, Transaction, WriteSetPayload}, - waypoint::Waypoint, write_set::WriteSetMut, }; - use std::{assert_eq, matches, str::FromStr, vec}; + use std::{assert_eq, matches, vec}; // Useful test constants const GENESIS_WAYPOINT: &str = @@ -228,6 +272,68 @@ mod test { assert!(!modified_config); } + #[test] + fn test_optimize_execution_config_non_genesis_mainnet() { + // Create a default node config + let mut node_config = NodeConfig::default(); + + // Verify the execution config does not have a genesis waypoint + assert!(&node_config.execution.genesis_waypoint.is_none()); + + // Inject a non-genesis waypoint into the base config + let non_genesis_waypoint = Waypoint::from_str(NON_GENESIS_WAYPOINT).unwrap(); + node_config.base.waypoint = WaypointConfig::FromConfig(non_genesis_waypoint); + + // Optimize the config for mainnet and verify modifications are made + let modified_config = ExecutionConfig::optimize( + &mut node_config, + &serde_yaml::from_str("{}").unwrap(), // An empty local config, + NodeType::Validator, + Some(ChainId::mainnet()), + ) + .unwrap(); + assert!(modified_config); + + // Verify that the mainnet genesis waypoint was injected into the execution config + let expected_genesis_waypoint = + WaypointConfig::FromConfig(Waypoint::from_str(MAINNET_GENESIS_WAYPOINT).unwrap()); + assert_eq!( + &node_config.execution.genesis_waypoint, + &Some(expected_genesis_waypoint) + ); + } + + #[test] + fn test_optimize_execution_config_non_genesis_testnet() { + // Create a default node config + let mut node_config = NodeConfig::default(); + + // Verify the execution config does not have a genesis waypoint + assert!(&node_config.execution.genesis_waypoint.is_none()); + + // Inject a non-genesis waypoint into the base config + let non_genesis_waypoint = Waypoint::from_str(NON_GENESIS_WAYPOINT).unwrap(); + node_config.base.waypoint = WaypointConfig::FromConfig(non_genesis_waypoint); + + // Optimize the config for testnet and verify modifications are made + let modified_config = ExecutionConfig::optimize( + &mut node_config, + &serde_yaml::from_str("{}").unwrap(), // An empty local config, + NodeType::PublicFullnode, + Some(ChainId::testnet()), + ) + .unwrap(); + assert!(modified_config); + + // Verify that the testnet genesis waypoint was injected into the execution config + let expected_genesis_waypoint = + WaypointConfig::FromConfig(Waypoint::from_str(TESTNET_GENESIS_WAYPOINT).unwrap()); + assert_eq!( + &node_config.execution.genesis_waypoint, + &Some(expected_genesis_waypoint) + ); + } + #[test] fn test_optimize_execution_config_skipped() { // Create a default node config