From f14817614c4ff4daaccf1d1539c196c948bfbc07 Mon Sep 17 00:00:00 2001 From: Greg Mitchell Date: Wed, 6 May 2026 13:43:01 +0000 Subject: [PATCH 1/3] serviceability: rename NewInterface to Interface and old enum to InterfaceDeprecated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final cross-cutting step in the forward-compatible Device interfaces refactor (issue #3663). Pure rename — no behavior change, no on-disk format change. The regenerated `device.bin` fixture is byte-identical to the committed one because Borsh is positional, not name-based. Interface (legacy enum) -> InterfaceDeprecated NewInterface (struct) -> Interface Device.new_interfaces field -> Device.interfaces Device.interfaces (legacy) -> Device.deprecated_interfaces Same shape applied to Go (`Device.NewInterfaces` / `Device.Interfaces` -> `Device.Interfaces` / `Device.DeprecatedInterfaces`), Python, and TypeScript SDKs. The `Interface` / `DeviceInterface` element type in each SDK already represents the canonical (new) format and needs no rename. The `activator/` crate was already deleted from the workspace, so the issue's "activator (minimal touch)" bullet is a no-op here. --- CHANGELOG.md | 6 + client/doublezero/src/command/connect.rs | 2 +- client/doublezero/src/dzd_latency.rs | 16 +- .../doublezero-admin/src/cli/device.rs | 4 +- .../doublezero-admin/src/cli/migrate.rs | 2 +- crates/sentinel/src/dz_ledger_reader.rs | 2 +- crates/sentinel/src/multicast_publisher.rs | 2 +- .../python/serviceability/state.py | 40 +-- .../serviceability/tests/test_fixtures.py | 50 ++-- ...est_new_interface.py => test_interface.py} | 36 +-- .../fixtures/generate-fixtures/src/main.rs | 67 ++--- .../typescript/serviceability/state.ts | 42 +-- .../serviceability/tests/fixtures.test.ts | 60 ++-- ...ew_interface.test.ts => interface.test.ts} | 36 +-- smartcontract/cli/src/device/create.rs | 2 +- smartcontract/cli/src/device/delete.rs | 2 +- smartcontract/cli/src/device/get.rs | 12 +- .../cli/src/device/interface/create.rs | 31 +- .../cli/src/device/interface/delete.rs | 20 +- smartcontract/cli/src/device/interface/get.rs | 15 +- .../cli/src/device/interface/list.rs | 30 +- .../cli/src/device/interface/update.rs | 49 ++-- smartcontract/cli/src/device/list.rs | 48 ++-- smartcontract/cli/src/device/sethealth.rs | 6 +- smartcontract/cli/src/device/update.rs | 16 +- smartcontract/cli/src/exchange/get.rs | 2 +- smartcontract/cli/src/exchange/list.rs | 4 +- smartcontract/cli/src/exchange/setdevice.rs | 2 +- smartcontract/cli/src/link/accept.rs | 24 +- smartcontract/cli/src/link/delete.rs | 4 +- smartcontract/cli/src/link/dzx_create.rs | 46 ++- smartcontract/cli/src/link/get.rs | 2 +- smartcontract/cli/src/link/list.rs | 18 +- smartcontract/cli/src/link/wan_create.rs | 64 ++--- smartcontract/cli/src/multicastgroup/get.rs | 2 +- smartcontract/cli/src/multicastgroup/list.rs | 4 +- smartcontract/cli/src/poll_for_activation.rs | 8 +- smartcontract/cli/src/resource/verify.rs | 4 +- smartcontract/cli/src/user/create.rs | 2 +- .../cli/src/user/create_subscribe.rs | 2 +- smartcontract/cli/src/user/get.rs | 4 +- smartcontract/cli/src/user/list.rs | 4 +- .../src/processors/device/create.rs | 2 +- .../src/processors/device/interface/create.rs | 9 +- .../src/processors/device/interface/update.rs | 2 +- .../src/processors/link/accept.rs | 2 +- .../src/processors/link/create.rs | 4 +- .../src/processors/topology/backfill.rs | 12 +- .../src/state/device.rs | 266 ++++++++---------- .../src/state/interface.rs | 155 +++++----- .../tests/delete_cyoa_interface_test.rs | 8 +- .../interface_onchain_allocation_test.rs | 33 +-- .../tests/link_wan_test.rs | 8 +- .../tests/topology_test.rs | 14 +- ...initialize_device_latency_samples_tests.rs | 4 +- .../sdk/go/serviceability/client_test.go | 8 +- .../sdk/go/serviceability/deserialize.go | 28 +- .../sdk/go/serviceability/deserialize_test.go | 48 ++-- .../sdk/go/serviceability/fixture_test.go | 62 ++-- smartcontract/sdk/go/serviceability/state.go | 20 +- .../sdk/rs/src/commands/device/delete.rs | 2 +- .../src/commands/device/interface/create.rs | 2 +- .../src/commands/device/interface/delete.rs | 2 +- .../src/commands/device/interface/remove.rs | 2 +- .../src/commands/device/interface/update.rs | 2 +- .../sdk/rs/src/commands/device/sethealth.rs | 3 +- .../sdk/rs/src/commands/device/update.rs | 9 +- .../sdk/rs/src/commands/topology/create.rs | 8 +- smartcontract/sdk/rs/src/lib.rs | 5 +- 69 files changed, 702 insertions(+), 810 deletions(-) rename sdk/serviceability/python/serviceability/tests/{test_new_interface.py => test_interface.py} (85%) rename sdk/serviceability/typescript/serviceability/tests/{new_interface.test.ts => interface.test.ts} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9c8afc1c..2e422fd4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file. ### Changes +- Smartcontract + - Canonicalize the forward-compatible Device interface names: rename the new struct from `NewInterface` to `Interface`, the legacy enum from `Interface` to `InterfaceDeprecated`, the `Device::new_interfaces` field to `Device::interfaces`, and the legacy `Device::interfaces` field to `Device::deprecated_interfaces`. On-disk format and behavior are unchanged — Borsh is positional, so the regenerated device fixture is byte-identical. Mechanical rename across the serviceability program, processors, CLI, sentinel, client, controlplane admin, and the Rust SDK. The activator/ crate was already deleted in an earlier change so the rename does not touch it ([#3663](https://github.com/malbeclabs/doublezero/issues/3663)) + - Stop reading `Device::deprecated_interfaces` outside of backward-compat tests. The `delete_device` processor and `Device::validate` now check the canonical `Device::interfaces` vec; the legacy slot is treated as wire-format-only state populated by the deserializer and projected by the serializer. CLI, processor, and Rust SDK construction sites use `..Default::default()` to avoid initializing the legacy field. The unused `UpdateDeviceCommand::deprecated_interfaces` SDK command field is removed. `Device::deprecated_interfaces` itself is retained for backward-compat fixture tests that verify legacy-slot decoding ([#3663](https://github.com/malbeclabs/doublezero/issues/3663)) + - Remove the `CurrentInterfaceVersion` type alias and the unused `Device::find_interface_legacy` helper. Tests that used to construct `CurrentInterfaceVersion {...}` (an `InterfaceV2` literal) and convert into either the canonical `Interface` or the legacy `InterfaceDeprecated` enum now build `Interface {...}` directly. The legacy enum's `into_current_version()` method is replaced by `to_v2()` for the few backward-compat sites that still need an `InterfaceV2` projection ([#3663](https://github.com/malbeclabs/doublezero/issues/3663)) +- SDK + - Apply the same rename in the Go, Python, and TypeScript serviceability readers: Go gets `Device.DeprecatedInterfaces` ↔ `Device.Interfaces` (with the new `Interfaces` taking the trailing-vec slot previously held by `NewInterfaces`); Python gets `Device.deprecated_interfaces` ↔ `Device.interfaces`; TypeScript gets `Device.deprecatedInterfaces` ↔ `Device.interfaces`. The `Interface` / `DeviceInterface` element type in each SDK already represents the canonical (new) format and needs no rename. Length-mismatch error messages now reference the canonical field names ([#3663](https://github.com/malbeclabs/doublezero/issues/3663)) - Smartcontract - Migrate read callers in the CLI, sentinel, client, controlplane admin, and Rust SDK topology helper to read interfaces from `Device::new_interfaces` instead of the legacy `interfaces` enum vec, and adopt the `Device::find_interface` signature that returns `&NewInterface`. The legacy `interfaces` slot is still written on-disk via the per-write V2 projection from #3667; this PR only migrates reads. The temporary `Device::find_interface_legacy` helper is retained for the smartcontract program processors, which migrate in a later issue. Activator is intentionally excluded — it is deprecated ([#3659](https://github.com/malbeclabs/doublezero/issues/3659)) - Activator diff --git a/client/doublezero/src/command/connect.rs b/client/doublezero/src/command/connect.rs index 675a8817f4..70b72e6d0c 100644 --- a/client/doublezero/src/command/connect.rs +++ b/client/doublezero/src/command/connect.rs @@ -1337,7 +1337,6 @@ mod tests { dz_prefixes: format!("10.{}.0.0/24", device_number).parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: @@ -1351,6 +1350,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; devices.insert(pk, device.clone()); (pk, device) diff --git a/client/doublezero/src/dzd_latency.rs b/client/doublezero/src/dzd_latency.rs index 1bd033434e..7e87d19827 100644 --- a/client/doublezero/src/dzd_latency.rs +++ b/client/doublezero/src/dzd_latency.rs @@ -9,7 +9,7 @@ pub(crate) fn get_device_tunnel_endpoints(device: &Device) -> Vec { let mut endpoints = vec![device.public_ip]; // Add all UserTunnelEndpoint interfaces - for iface in &device.new_interfaces { + for iface in &device.interfaces { if iface.user_tunnel_endpoint && iface.ip_net != Default::default() { endpoints.push(iface.ip_net.ip()); } @@ -236,8 +236,8 @@ mod tests { use crate::servicecontroller::{LatencyRecord, LatencyResponse, MockServiceController}; use doublezero_program_common::types::{NetworkV4, NetworkV4List}; use doublezero_sdk::{ - AccountType, CurrentInterfaceVersion, Device, DeviceStatus, DeviceType, Interface, - InterfaceStatus, InterfaceType, LoopbackType, + AccountType, Device, DeviceStatus, DeviceType, Interface, InterfaceStatus, InterfaceType, + LoopbackType, }; use doublezero_serviceability::state::interface::{InterfaceCYOA, InterfaceDIA, RoutingMode}; use solana_sdk::pubkey::Pubkey; @@ -254,10 +254,10 @@ mod tests { tunnel_endpoint_ips: Vec, ) -> (Pubkey, Device) { let pubkey = Pubkey::new_unique(); - let v2_ifaces: Vec = tunnel_endpoint_ips + let interfaces: Vec = tunnel_endpoint_ips .into_iter() .enumerate() - .map(|(i, ip)| CurrentInterfaceVersion { + .map(|(i, ip)| Interface { status: InterfaceStatus::Activated, name: format!("Loopback{}", i), interface_type: InterfaceType::Loopback, @@ -272,11 +272,9 @@ mod tests { ip_net: NetworkV4::new(ip, 32).unwrap(), node_segment_idx: 0, user_tunnel_endpoint: true, + ..Default::default() }) .collect(); - let interfaces: Vec = - v2_ifaces.iter().map(|v| Interface::V2(v.clone())).collect(); - let new_interfaces = v2_ifaces.iter().map(|v| v.try_into().unwrap()).collect(); ( pubkey, Device { @@ -295,7 +293,6 @@ mod tests { contributor_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces, - new_interfaces, reference_count: 0, users_count, max_users: 1, @@ -310,6 +307,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }, ) } diff --git a/controlplane/doublezero-admin/src/cli/device.rs b/controlplane/doublezero-admin/src/cli/device.rs index 4d58252262..5bacf6090e 100644 --- a/controlplane/doublezero-admin/src/cli/device.rs +++ b/controlplane/doublezero-admin/src/cli/device.rs @@ -160,7 +160,6 @@ impl MigrateMulticastCountsCommand { contributor_pk: None, location_pk: None, mgmt_vrf: None, - interfaces: None, max_users: None, users_count: None, status: None, @@ -257,7 +256,6 @@ impl MigrateUnicastCountsCommand { contributor_pk: None, location_pk: None, mgmt_vrf: None, - interfaces: None, max_users: None, users_count: None, status: None, @@ -312,7 +310,6 @@ mod tests { dz_prefixes: "10.0.0.1/32".parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -324,6 +321,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: pub_count, max_multicast_publishers: 0, + ..Default::default() } } diff --git a/controlplane/doublezero-admin/src/cli/migrate.rs b/controlplane/doublezero-admin/src/cli/migrate.rs index 2af090f751..29a0d058be 100644 --- a/controlplane/doublezero-admin/src/cli/migrate.rs +++ b/controlplane/doublezero-admin/src/cli/migrate.rs @@ -106,7 +106,7 @@ impl FlexAlgoMigrateCliCommand { let mut devices_needing_backfill: Vec = Vec::new(); for (device_pubkey, device) in &device_entries { - let needs_backfill = device.new_interfaces.iter().any(|iface| { + let needs_backfill = device.interfaces.iter().any(|iface| { iface.loopback_type == LoopbackType::Vpnv4 && !iface .flex_algo_node_segments diff --git a/crates/sentinel/src/dz_ledger_reader.rs b/crates/sentinel/src/dz_ledger_reader.rs index a5199e5bc1..56199a5ef8 100644 --- a/crates/sentinel/src/dz_ledger_reader.rs +++ b/crates/sentinel/src/dz_ledger_reader.rs @@ -286,7 +286,7 @@ pub fn fetch_device_infos( .copied() .unwrap_or((0.0, 0.0)); let user_tunnel_endpoints = device - .new_interfaces + .interfaces .iter() .filter_map(|iface| { if iface.user_tunnel_endpoint && iface.ip_net != Default::default() { diff --git a/crates/sentinel/src/multicast_publisher.rs b/crates/sentinel/src/multicast_publisher.rs index 2684ee553b..be3c1afeaa 100644 --- a/crates/sentinel/src/multicast_publisher.rs +++ b/crates/sentinel/src/multicast_publisher.rs @@ -468,7 +468,7 @@ impl MulticastDzLedgerClient for RpcMulticastDzLedgerClient { continue; } let user_tunnel_endpoints = device - .new_interfaces + .interfaces .iter() .filter_map(|iface| { if iface.user_tunnel_endpoint && iface.ip_net != Default::default() { diff --git a/sdk/serviceability/python/serviceability/state.py b/sdk/serviceability/python/serviceability/state.py index f1067619b1..521cf483e6 100644 --- a/sdk/serviceability/python/serviceability/state.py +++ b/sdk/serviceability/python/serviceability/state.py @@ -401,7 +401,7 @@ def __str__(self) -> str: # Account dataclasses # --------------------------------------------------------------------------- -# On-wire schema version for the size-prefixed NewInterface format +# On-wire schema version for the size-prefixed Interface format # (matches Rust's CURRENT_INTERFACE_SCHEMA_VERSION). Note: prior to issue #3660 # this constant gated the legacy enum reader at value 2 (max known disc=1); it # is now bumped to 4 to match the size-prefixed schema. Legacy enum reads still @@ -482,7 +482,7 @@ def from_reader(cls, r: DefensiveReader) -> Interface: @classmethod def from_reader_sized(cls, r: DefensiveReader) -> Interface: - """Read a single size-prefixed NewInterface element. + """Read a single size-prefixed Interface element. Wire format: u16 size (incl. 3-byte prefix) + u8 version + body. After reading the known body fields, the reader is advanced to start+size so @@ -681,7 +681,7 @@ class Device: metrics_publisher_pub_key: Pubkey = Pubkey.default() contributor_pub_key: Pubkey = Pubkey.default() mgmt_vrf: str = "" - interfaces: list[Interface] = field(default_factory=list) + deprecated_interfaces: list[Interface] = field(default_factory=list) reference_count: int = 0 users_count: int = 0 max_users: int = 0 @@ -694,11 +694,11 @@ class Device: reserved_seats: int = 0 multicast_publishers_count: int = 0 max_multicast_publishers: int = 0 - # new_interfaces is the trailing size-prefixed vec parallel to interfaces. For - # legacy accounts (no trailing bytes), this is rebuilt from interfaces by - # from_bytes. When populated from the wire, len(new_interfaces) == - # len(interfaces) is enforced. - new_interfaces: list[Interface] = field(default_factory=list) + # interfaces is the trailing size-prefixed vec parallel to deprecated_interfaces. + # For legacy accounts (no trailing bytes), this is rebuilt from deprecated_interfaces + # by from_bytes. When populated from the wire, len(interfaces) == + # len(deprecated_interfaces) is enforced. + interfaces: list[Interface] = field(default_factory=list) @classmethod def from_bytes(cls, data: bytes) -> Device: @@ -719,7 +719,7 @@ def from_bytes(cls, data: bytes) -> Device: dev.contributor_pub_key = _read_pubkey(r) dev.mgmt_vrf = r.read_string() iface_len = r.read_u32() - dev.interfaces = [Interface.from_reader(r) for _ in range(iface_len)] + dev.deprecated_interfaces = [Interface.from_reader(r) for _ in range(iface_len)] dev.reference_count = r.read_u32() dev.users_count = r.read_u16() dev.max_users = r.read_u16() @@ -733,13 +733,13 @@ def from_bytes(cls, data: bytes) -> Device: dev.multicast_publishers_count = r.read_u16() dev.max_multicast_publishers = r.read_u16() - # Trailing new_interfaces vec (size-prefixed). Empty trailing => rebuild - # from legacy. Non-empty trailing whose declared length differs from the - # legacy interfaces length is a corrupt-account condition; raise per - # Rust device-reader semantics (length mismatch is fatal). + # Trailing interfaces vec (size-prefixed). Empty trailing => rebuild + # from deprecated_interfaces. Non-empty trailing whose declared length + # differs from the deprecated_interfaces length is a corrupt-account + # condition; raise per Rust device-reader semantics (length mismatch is fatal). if r.remaining == 0: - dev.new_interfaces = [] - for legacy in dev.interfaces: + dev.interfaces = [] + for legacy in dev.deprecated_interfaces: rebuilt = Interface( size=0, version=CURRENT_INTERFACE_VERSION, @@ -759,15 +759,15 @@ def from_bytes(cls, data: bytes) -> Device: user_tunnel_endpoint=legacy.user_tunnel_endpoint, flex_algo_node_segments=list(legacy.flex_algo_node_segments), ) - dev.new_interfaces.append(rebuilt) + dev.interfaces.append(rebuilt) else: new_len = r.read_u32() - if new_len != len(dev.interfaces): + if new_len != len(dev.deprecated_interfaces): raise ValueError( - f"Device new_interfaces length {new_len} != " - f"interfaces length {len(dev.interfaces)}" + f"Device interfaces length {new_len} != " + f"deprecated_interfaces length {len(dev.deprecated_interfaces)}" ) - dev.new_interfaces = [ + dev.interfaces = [ Interface.from_reader_sized(r) for _ in range(new_len) ] diff --git a/sdk/serviceability/python/serviceability/tests/test_fixtures.py b/sdk/serviceability/python/serviceability/tests/test_fixtures.py index a088b1981c..d58113c252 100644 --- a/sdk/serviceability/python/serviceability/tests/test_fixtures.py +++ b/sdk/serviceability/python/serviceability/tests/test_fixtures.py @@ -170,22 +170,22 @@ def test_deserialize(self): ) # Legacy slot is the V2 projection of new_interfaces (always V2 per #3653); # both entries carry version 1 and no FlexAlgoNodeSegments. + assert len(dev.deprecated_interfaces) == 2 + assert dev.deprecated_interfaces[0].version == 1 + assert dev.deprecated_interfaces[0].name == "Loopback0" + assert dev.deprecated_interfaces[0].flex_algo_node_segments == [] + assert dev.deprecated_interfaces[1].version == 1 + assert dev.deprecated_interfaces[1].name == "Ethernet1" + # Trailing new_interfaces vec carries the full V4 Interface bodies. assert len(dev.interfaces) == 2 - assert dev.interfaces[0].version == 1 - assert dev.interfaces[0].name == "Loopback0" - assert dev.interfaces[0].flex_algo_node_segments == [] - assert dev.interfaces[1].version == 1 - assert dev.interfaces[1].name == "Ethernet1" - # Trailing new_interfaces vec carries the full V4 NewInterface bodies. - assert len(dev.new_interfaces) == 2 - ni0 = dev.new_interfaces[0] + ni0 = dev.interfaces[0] assert ni0.version == CURRENT_INTERFACE_VERSION assert ni0.name == "Loopback0" assert ni0.loopback_type.value == 1 # Vpnv4 assert len(ni0.flex_algo_node_segments) == 1 assert ni0.flex_algo_node_segments[0].node_segment_idx == 300 assert ni0.size == _expected_new_interface_size(ni0) - ni1 = dev.new_interfaces[1] + ni1 = dev.interfaces[1] assert ni1.version == CURRENT_INTERFACE_VERSION assert ni1.name == "Ethernet1" assert ni1.user_tunnel_endpoint is True @@ -206,10 +206,10 @@ def test_deserialize(self): def _expected_new_interface_size(ni) -> int: - """Recompute the on-disk size for a NewInterface element so tests don't bake + """Recompute the on-disk size for a Interface element so tests don't bake body byte counts as magic numbers. - Layout (matches Rust NewInterface::serialize_body, interface.rs:641-658): + Layout (matches Rust Interface::serialize_body, interface.rs:641-658): u16 size + u8 version (3 bytes prefix) + u8 status + (u32+len) name + u8 interface_type + u8 cyoa + u8 dia + u8 loopback_type + u64 bandwidth + u64 cir + u16 mtu + u8 routing_mode + @@ -234,22 +234,22 @@ def test_deserialize(self): dev = Device.from_bytes(data) assert meta["name"] == "DeviceLegacy" # Legacy slot mirrors the original V1+V2 hand-serialized shape. - assert len(dev.interfaces) == 2 - assert dev.interfaces[0].version == 0 # V1 - assert dev.interfaces[0].name == "Loopback0" - assert dev.interfaces[1].version == 1 # V2 - assert dev.interfaces[1].name == "Ethernet1" + assert len(dev.deprecated_interfaces) == 2 + assert dev.deprecated_interfaces[0].version == 0 # V1 + assert dev.deprecated_interfaces[0].name == "Loopback0" + assert dev.deprecated_interfaces[1].version == 1 # V2 + assert dev.deprecated_interfaces[1].name == "Ethernet1" # Rebuilt new_interfaces: same field values as the legacy entries, but # stamped with the current schema version and zero on-disk size. - assert len(dev.new_interfaces) == 2 - for ni in dev.new_interfaces: + assert len(dev.interfaces) == 2 + for ni in dev.interfaces: assert ni.version == CURRENT_INTERFACE_VERSION assert ni.size == 0 assert ni.flex_algo_node_segments == [] - assert dev.new_interfaces[0].name == "Loopback0" - assert dev.new_interfaces[0].loopback_type.value == 1 # Vpnv4 - assert dev.new_interfaces[1].name == "Ethernet1" - assert dev.new_interfaces[1].user_tunnel_endpoint is True + assert dev.interfaces[0].name == "Loopback0" + assert dev.interfaces[0].loopback_type.value == 1 # Vpnv4 + assert dev.interfaces[1].name == "Ethernet1" + assert dev.interfaces[1].user_tunnel_endpoint is True class TestFixtureDeviceFutureVersion: @@ -263,16 +263,16 @@ def test_deserialize(self): data, meta = _load_fixture("device_future_version") dev = Device.from_bytes(data) assert meta["name"] == "DeviceFutureVersion" - assert len(dev.new_interfaces) == 2 + assert len(dev.interfaces) == 2 # First element parses normally at the current schema version. - ni0 = dev.new_interfaces[0] + ni0 = dev.interfaces[0] assert ni0.version == CURRENT_INTERFACE_VERSION assert ni0.name == "Loopback0" assert len(ni0.flex_algo_node_segments) == 1 # Second element has the future-version stamp + extra trailing bytes; # known body fields still parse correctly because the reader advances # to start+size. - ni1 = dev.new_interfaces[1] + ni1 = dev.interfaces[1] assert ni1.version == 5 assert ni1.size == _expected_new_interface_size(ni1) + 8 assert ni1.name == "Ethernet1" diff --git a/sdk/serviceability/python/serviceability/tests/test_new_interface.py b/sdk/serviceability/python/serviceability/tests/test_interface.py similarity index 85% rename from sdk/serviceability/python/serviceability/tests/test_new_interface.py rename to sdk/serviceability/python/serviceability/tests/test_interface.py index b715d8038f..1d9f3ae6cb 100644 --- a/sdk/serviceability/python/serviceability/tests/test_new_interface.py +++ b/sdk/serviceability/python/serviceability/tests/test_interface.py @@ -1,7 +1,7 @@ -"""Hand-built byte-vector tests for the size-prefixed NewInterface reader. +"""Hand-built byte-vector tests for the size-prefixed Interface reader. The wire format mirrors smartcontract/programs/doublezero-serviceability::state::device: -each NewInterface element is (u16 size, u8 version, body), where size includes +each Interface element is (u16 size, u8 version, body), where size includes the 3-byte prefix. The Device account stores this vec immediately after max_multicast_publishers. """ @@ -36,7 +36,7 @@ def _string(s: str) -> bytes: def _new_interface_body(name: str) -> bytes: - """Body bytes for a minimal V4 NewInterface with caller-provided name.""" + """Body bytes for a minimal V4 Interface with caller-provided name.""" parts: list[bytes] = [] parts.append(b"\x00") # status parts.append(_string(name)) @@ -128,29 +128,29 @@ def test_populated_trailing_vec(): raw = _device(2, ["Eth1", "Lo0"], trailing) dev = Device.from_bytes(raw) + assert len(dev.deprecated_interfaces) == 2 assert len(dev.interfaces) == 2 - assert len(dev.new_interfaces) == 2 - assert dev.new_interfaces[0].name == "Eth1" - assert dev.new_interfaces[1].name == "Lo0" - assert dev.new_interfaces[0].version == CURRENT_INTERFACE_VERSION - for i, ni in enumerate(dev.new_interfaces): + assert dev.interfaces[0].name == "Eth1" + assert dev.interfaces[1].name == "Lo0" + assert dev.interfaces[0].version == CURRENT_INTERFACE_VERSION + for i, ni in enumerate(dev.interfaces): expected_size = 3 + len(_new_interface_body(ni.name)) assert ni.size == expected_size, ( f"size mismatch on element {i}: expected {expected_size}, got {ni.size}" ) -def test_legacy_account_rebuilds_new_interfaces(): +def test_legacy_account_rebuilds_interfaces(): raw = _device(2, ["Eth1", "Lo0"], trailing=None) dev = Device.from_bytes(raw) + assert len(dev.deprecated_interfaces) == 2 assert len(dev.interfaces) == 2 - assert len(dev.new_interfaces) == 2 - assert dev.new_interfaces[0].name == "Eth1" - assert dev.new_interfaces[1].name == "Lo0" + assert dev.interfaces[0].name == "Eth1" + assert dev.interfaces[1].name == "Lo0" # Rebuilt entries are stamped with the current schema version and zero size # (callers don't need on-disk size for a rebuild). - for ni in dev.new_interfaces: + for ni in dev.interfaces: assert ni.version == CURRENT_INTERFACE_VERSION assert ni.size == 0 @@ -159,7 +159,7 @@ def test_trailing_length_mismatch_raises(): trailing = _u32(1) + _new_interface_sized("Eth1") # only 1, but legacy has 2 raw = _device(2, ["Eth1", "Lo0"], trailing) - with pytest.raises(ValueError, match="length 1 != interfaces length 2"): + with pytest.raises(ValueError, match="interfaces length 1 != deprecated_interfaces length 2"): Device.from_bytes(raw) @@ -173,8 +173,8 @@ def test_future_version_skips_trailing_bytes(): raw = _device(1, ["Future1"], trailing) dev = Device.from_bytes(raw) - assert len(dev.new_interfaces) == 1 - assert dev.new_interfaces[0].version == 5 - assert dev.new_interfaces[0].size == 3 + len(body) + assert len(dev.interfaces) == 1 + assert dev.interfaces[0].version == 5 + assert dev.interfaces[0].size == 3 + len(body) # Body fields up to known shape are still parsed. - assert dev.new_interfaces[0].name == "Future1" + assert dev.interfaces[0].name == "Future1" diff --git a/sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs b/sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs index 53d30da397..b9eb07a8cd 100644 --- a/sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs +++ b/sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs @@ -27,8 +27,9 @@ use doublezero_serviceability::state::{ globalconfig::GlobalConfig, globalstate::GlobalState, interface::{ - Interface, InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, InterfaceV1, - InterfaceV2, LoopbackType, NewInterface, RoutingMode, CURRENT_INTERFACE_SCHEMA_VERSION, + Interface, InterfaceCYOA, InterfaceDIA, InterfaceDeprecated, InterfaceStatus, + InterfaceType, InterfaceV1, InterfaceV2, LoopbackType, RoutingMode, + CURRENT_INTERFACE_SCHEMA_VERSION, }, link::{Link, LinkDesiredStatus, LinkHealth, LinkLinkType, LinkStatus}, location::{Location, LocationStatus}, @@ -281,9 +282,9 @@ fn generate_exchange(dir: &Path) { } /// Build a canonical `Device` value used by `device.bin` and `device_future_version.bin`. -/// The trailing `new_interfaces` vec carries one Vpnv4 loopback with a `FlexAlgoNodeSegment` -/// and one physical user-tunnel-endpoint. `interfaces: vec![]` because the custom Device -/// serializer projects the legacy on-disk slot from `new_interfaces` (always V2 per #3653). +/// The trailing `interfaces` vec carries one Vpnv4 loopback with a `FlexAlgoNodeSegment` +/// and one physical user-tunnel-endpoint. `deprecated_interfaces: vec![]` because the custom Device +/// serializer projects the legacy on-disk slot from `interfaces` (always V2 per #3653). fn canonical_device() -> ( Device, solana_program::pubkey::Pubkey, // owner @@ -300,10 +301,10 @@ fn canonical_device() -> ( let contributor_pk = pubkey_from_byte(0x44); let topology_pk = pubkey_from_byte(0x45); - // size/version on the in-memory NewInterface are recomputed by the BorshSerialize + // size/version on the in-memory Interface are recomputed by the BorshSerialize // impl (interface.rs:687-688) — leave them at 0 here. - let new_interfaces = vec![ - NewInterface { + let interfaces = vec![ + Interface { size: 0, version: 0, status: InterfaceStatus::Activated, @@ -325,7 +326,7 @@ fn canonical_device() -> ( node_segment_idx: 300, }], }, - NewInterface { + Interface { size: 0, version: 0, status: InterfaceStatus::Activated, @@ -361,8 +362,8 @@ fn canonical_device() -> ( metrics_publisher_pk, contributor_pk, mgmt_vrf: "mgmt".into(), - interfaces: vec![], - new_interfaces, + deprecated_interfaces: vec![], + interfaces, reference_count: 12, users_count: 5, max_users: 100, @@ -381,9 +382,9 @@ fn canonical_device() -> ( } /// Common `meta.fields` describing the canonical Device: legacy slot is the V2 projection -/// of `new_interfaces` (both elements have `Version = 1`, no FlexAlgoNodeSegments per +/// of `interfaces` (both elements have `Version = 1`, no FlexAlgoNodeSegments per /// device.rs:527-534 / interface.rs:793-813); the trailing vec carries the full V4 -/// NewInterface bodies including `flex_algo_node_segments`. +/// Interface bodies including `flex_algo_node_segments`. #[allow(clippy::too_many_arguments)] fn canonical_device_fields( owner: &solana_program::pubkey::Pubkey, @@ -415,7 +416,7 @@ fn canonical_device_fields( FieldValue { name: "ContributorPk".into(), value: pubkey_bs58(contributor_pk), typ: "pubkey".into() }, FieldValue { name: "MgmtVrf".into(), value: "mgmt".into(), typ: "string".into() }, FieldValue { name: "InterfacesLen".into(), value: "2".into(), typ: "u32".into() }, - // Interface 0 - V2 projection of NewInterface[0] (Loopback Vpnv4). + // Interface 0 - V2 projection of Interface[0] (Loopback Vpnv4). FieldValue { name: "Interface0Version".into(), value: "1".into(), typ: "u8".into() }, FieldValue { name: "Interface0Status".into(), value: "3".into(), typ: "u8".into() }, FieldValue { name: "Interface0Name".into(), value: "Loopback0".into(), typ: "string".into() }, @@ -431,7 +432,7 @@ fn canonical_device_fields( FieldValue { name: "Interface0IpNet".into(), value: "10.0.0.1/32".into(), typ: "networkv4".into() }, FieldValue { name: "Interface0NodeSegmentIdx".into(), value: "100".into(), typ: "u16".into() }, FieldValue { name: "Interface0UserTunnelEndpoint".into(), value: "false".into(), typ: "bool".into() }, - // Interface 1 - V2 projection of NewInterface[1] (Physical user-tunnel-endpoint). + // Interface 1 - V2 projection of Interface[1] (Physical user-tunnel-endpoint). FieldValue { name: "Interface1Version".into(), value: "1".into(), typ: "u8".into() }, FieldValue { name: "Interface1Status".into(), value: "3".into(), typ: "u8".into() }, FieldValue { name: "Interface1Name".into(), value: "Ethernet1".into(), typ: "string".into() }, @@ -459,9 +460,9 @@ fn canonical_device_fields( FieldValue { name: "ReservedSeats".into(), value: "3".into(), typ: "u16".into() }, FieldValue { name: "MulticastPublishersCount".into(), value: "1".into(), typ: "u16".into() }, FieldValue { name: "MaxMulticastPublishers".into(), value: "10".into(), typ: "u16".into() }, - // Trailing new_interfaces vec. + // Trailing interfaces vec. FieldValue { name: "NewInterfacesLen".into(), value: "2".into(), typ: "u32".into() }, - // NewInterface 0 - Loopback Vpnv4 with one FlexAlgoNodeSegment. + // Interface 0 - Loopback Vpnv4 with one FlexAlgoNodeSegment. FieldValue { name: "NewInterface0Size".into(), value: new_interface0_size.to_string(), typ: "u16".into() }, FieldValue { name: "NewInterface0Version".into(), value: new_interface0_version.to_string(), typ: "u8".into() }, FieldValue { name: "NewInterface0Status".into(), value: "3".into(), typ: "u8".into() }, @@ -481,7 +482,7 @@ fn canonical_device_fields( FieldValue { name: "NewInterface0FlexAlgoNodeSegmentsLen".into(), value: "1".into(), typ: "u32".into() }, FieldValue { name: "NewInterface0FlexAlgoNodeSegments0Topology".into(), value: pubkey_bs58(topology_pk), typ: "pubkey".into() }, FieldValue { name: "NewInterface0FlexAlgoNodeSegments0NodeSegmentIdx".into(), value: "300".into(), typ: "u16".into() }, - // NewInterface 1 - Physical user-tunnel-endpoint, no flex segments. + // Interface 1 - Physical user-tunnel-endpoint, no flex segments. FieldValue { name: "NewInterface1Size".into(), value: new_interface1_size.to_string(), typ: "u16".into() }, FieldValue { name: "NewInterface1Version".into(), value: new_interface1_version.to_string(), typ: "u8".into() }, FieldValue { name: "NewInterface1Status".into(), value: "3".into(), typ: "u8".into() }, @@ -507,13 +508,13 @@ fn generate_device(dir: &Path) { canonical_device(); // Use Device's custom BorshSerialize: projects the on-disk legacy `interfaces` - // slot from `new_interfaces` (always V2 per #3653/#3667) and appends the - // size-prefixed `new_interfaces` vec. SDKs read both, with the trailing vec + // slot from `interfaces` (always V2 per #3653/#3667) and appends the + // size-prefixed `interfaces` vec. SDKs read both, with the trailing vec // taking precedence over the rebuilt-from-legacy fallback. let data = borsh::to_vec(&val).unwrap(); - let size0 = val.new_interfaces[0].compute_on_disk_size().unwrap(); - let size1 = val.new_interfaces[1].compute_on_disk_size().unwrap(); + let size0 = val.interfaces[0].compute_on_disk_size().unwrap(); + let size1 = val.interfaces[1].compute_on_disk_size().unwrap(); let meta = FixtureMeta { name: "Device".into(), @@ -529,8 +530,8 @@ fn generate_device(dir: &Path) { } /// Hand-serialized device with the legacy `interfaces` vec populated and **no** trailing -/// `new_interfaces` vec — the pre-#3667 on-disk format. SDKs detect the absent trailing -/// bytes and rebuild `new_interfaces` from the legacy enum vec, stamping each entry with +/// `interfaces` vec — the pre-#3667 on-disk format. SDKs detect the absent trailing +/// bytes and rebuild `interfaces` from the legacy enum vec, stamping each entry with /// `Version = CURRENT_INTERFACE_VERSION` and `Size = 0`. fn generate_device_legacy(dir: &Path) { let owner = pubkey_from_byte(0x40); @@ -554,8 +555,8 @@ fn generate_device_legacy(dir: &Path) { metrics_publisher_pk, contributor_pk, mgmt_vrf: "mgmt".into(), - interfaces: vec![ - Interface::V1(InterfaceV1 { + deprecated_interfaces: vec![ + InterfaceDeprecated::V1(InterfaceV1 { status: InterfaceStatus::Activated, name: "Loopback0".into(), interface_type: InterfaceType::Loopback, @@ -565,7 +566,7 @@ fn generate_device_legacy(dir: &Path) { node_segment_idx: 100, user_tunnel_endpoint: false, }), - Interface::V2(InterfaceV2 { + InterfaceDeprecated::V2(InterfaceV2 { status: InterfaceStatus::Activated, name: "Ethernet1".into(), interface_type: InterfaceType::Physical, @@ -582,7 +583,7 @@ fn generate_device_legacy(dir: &Path) { user_tunnel_endpoint: true, }), ], - new_interfaces: vec![], + interfaces: vec![], reference_count: 12, users_count: 5, max_users: 100, @@ -597,7 +598,7 @@ fn generate_device_legacy(dir: &Path) { max_multicast_publishers: 10, }; - // Bypass Device::serialize so we don't write the trailing new_interfaces vec — + // Bypass Device::serialize so we don't write the trailing interfaces vec — // this is exactly the pre-#3667 byte shape the SDK legacy-fallback path consumes. let mut data = Vec::new(); BorshSerialize::serialize(&val.account_type, &mut data).unwrap(); @@ -614,7 +615,7 @@ fn generate_device_legacy(dir: &Path) { BorshSerialize::serialize(&val.metrics_publisher_pk, &mut data).unwrap(); BorshSerialize::serialize(&val.contributor_pk, &mut data).unwrap(); BorshSerialize::serialize(&val.mgmt_vrf, &mut data).unwrap(); - BorshSerialize::serialize(&val.interfaces, &mut data).unwrap(); + BorshSerialize::serialize(&val.deprecated_interfaces, &mut data).unwrap(); BorshSerialize::serialize(&val.reference_count, &mut data).unwrap(); BorshSerialize::serialize(&val.users_count, &mut data).unwrap(); BorshSerialize::serialize(&val.max_users, &mut data).unwrap(); @@ -687,7 +688,7 @@ fn generate_device_legacy(dir: &Path) { FieldValue { name: "ReservedSeats".into(), value: "3".into(), typ: "u16".into() }, FieldValue { name: "MulticastPublishersCount".into(), value: "1".into(), typ: "u16".into() }, FieldValue { name: "MaxMulticastPublishers".into(), value: "10".into(), typ: "u16".into() }, - // Rebuilt new_interfaces (size=0, version=current); both bodies mirror + // Rebuilt interfaces (size=0, version=current); both bodies mirror // the V2 projection of the legacy entries — V1's missing fields default per // `InterfaceV2::try_from(&InterfaceV1)` (interface.rs:353-374). FieldValue { name: "NewInterfacesLen".into(), value: "2".into(), typ: "u32".into() }, @@ -745,7 +746,7 @@ fn generate_device_future_version(dir: &Path) { // The trailing vec elements are written contiguously at end-of-buffer; the last // element ends exactly at buf.len(). Locate its size+version header by subtracting // the precomputed on-disk size. - let last = val.new_interfaces.last().expect("non-empty"); + let last = val.interfaces.last().expect("non-empty"); let last_size = last.compute_on_disk_size().unwrap(); let new_last_size = last_size + FUTURE_VERSION_JUNK as u16; let last_start = data.len() - last_size as usize; @@ -755,7 +756,7 @@ fn generate_device_future_version(dir: &Path) { data[last_start + 2] = FUTURE_VERSION; data.extend(std::iter::repeat_n(0xAB, FUTURE_VERSION_JUNK)); - let size0 = val.new_interfaces[0].compute_on_disk_size().unwrap(); + let size0 = val.interfaces[0].compute_on_disk_size().unwrap(); let meta = FixtureMeta { name: "DeviceFutureVersion".into(), diff --git a/sdk/serviceability/typescript/serviceability/state.ts b/sdk/serviceability/typescript/serviceability/state.ts index 9d9bb70461..85cdb93ed6 100644 --- a/sdk/serviceability/typescript/serviceability/state.ts +++ b/sdk/serviceability/typescript/serviceability/state.ts @@ -498,7 +498,7 @@ export interface DeviceInterface { flexAlgoNodeSegments?: FlexAlgoNodeSegment[]; } -// On-wire schema version for the size-prefixed NewInterface format +// On-wire schema version for the size-prefixed Interface format // (matches Rust's CURRENT_INTERFACE_SCHEMA_VERSION). Note: prior to issue #3660 // this constant gated the legacy enum reader at value 2 (max known disc=1); it // is now bumped to 4 to match the size-prefixed schema. @@ -573,7 +573,7 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { return iface; } -// deserializeInterfaceSized reads a single size-prefixed NewInterface element. +// deserializeInterfaceSized reads a single size-prefixed Interface element. // // Wire format: u16 size + u8 version + body, where size includes the 3-byte // prefix. Forward-compat readers always advance the cursor to start+size after @@ -655,7 +655,7 @@ export interface Device { metricsPublisherPubKey: PublicKey; contributorPubKey: PublicKey; mgmtVrf: string; - interfaces: DeviceInterface[]; + deprecatedInterfaces: DeviceInterface[]; referenceCount: number; usersCount: number; maxUsers: number; @@ -668,11 +668,11 @@ export interface Device { reservedSeats: number; multicastPublishersCount: number; maxMulticastPublishers: number; - // newInterfaces is the trailing size-prefixed vec parallel to interfaces. For - // legacy accounts (no trailing bytes), this is rebuilt from interfaces by - // deserializeDevice. When populated from the wire, length parity with - // interfaces is enforced. - newInterfaces: DeviceInterface[]; + // interfaces is the trailing size-prefixed vec parallel to deprecatedInterfaces. + // For legacy accounts (no trailing bytes), this is rebuilt from + // deprecatedInterfaces by deserializeDevice. When populated from the wire, + // length parity with deprecatedInterfaces is enforced. + interfaces: DeviceInterface[]; } export function deserializeDevice(data: Uint8Array): Device { @@ -693,9 +693,9 @@ export function deserializeDevice(data: Uint8Array): Device { const mgmtVrf = r.readString(); const ifaceLen = r.readU32(); - const interfaces: DeviceInterface[] = []; + const deprecatedInterfaces: DeviceInterface[] = []; for (let i = 0; i < ifaceLen; i++) { - interfaces.push(deserializeInterface(r)); + deprecatedInterfaces.push(deserializeInterface(r)); } const referenceCount = r.readU32(); @@ -711,12 +711,12 @@ export function deserializeDevice(data: Uint8Array): Device { const multicastPublishersCount = r.readU16(); const maxMulticastPublishers = r.readU16(); - // Trailing new_interfaces vec (size-prefixed). Empty trailing => rebuild - // from legacy. Non-empty trailing whose declared length differs from the - // legacy interfaces length is a corrupt-account condition. - let newInterfaces: DeviceInterface[]; + // Trailing interfaces vec (size-prefixed). Empty trailing => rebuild from + // deprecatedInterfaces. Non-empty trailing whose declared length differs from + // deprecatedInterfaces length is a corrupt-account condition. + let interfaces: DeviceInterface[]; if (r.remaining === 0) { - newInterfaces = interfaces.map((legacy) => ({ + interfaces = deprecatedInterfaces.map((legacy) => ({ ...legacy, size: 0, version: CURRENT_INTERFACE_VERSION, @@ -726,14 +726,14 @@ export function deserializeDevice(data: Uint8Array): Device { })); } else { const newLen = r.readU32(); - if (newLen !== interfaces.length) { + if (newLen !== deprecatedInterfaces.length) { throw new Error( - `Device new_interfaces length ${newLen} != interfaces length ${interfaces.length}`, + `Device interfaces length ${newLen} != deprecatedInterfaces length ${deprecatedInterfaces.length}`, ); } - newInterfaces = []; + interfaces = []; for (let i = 0; i < newLen; i++) { - newInterfaces.push(deserializeInterfaceSized(r)); + interfaces.push(deserializeInterfaceSized(r)); } } @@ -752,7 +752,7 @@ export function deserializeDevice(data: Uint8Array): Device { metricsPublisherPubKey, contributorPubKey, mgmtVrf, - interfaces, + deprecatedInterfaces, referenceCount, usersCount, maxUsers, @@ -765,7 +765,7 @@ export function deserializeDevice(data: Uint8Array): Device { reservedSeats, multicastPublishersCount, maxMulticastPublishers, - newInterfaces, + interfaces, }; } diff --git a/sdk/serviceability/typescript/serviceability/tests/fixtures.test.ts b/sdk/serviceability/typescript/serviceability/tests/fixtures.test.ts index 754a38b1c7..2be1c3cbb6 100644 --- a/sdk/serviceability/typescript/serviceability/tests/fixtures.test.ts +++ b/sdk/serviceability/typescript/serviceability/tests/fixtures.test.ts @@ -148,12 +148,12 @@ describe("Exchange fixture", () => { // Matches Rust CURRENT_INTERFACE_SCHEMA_VERSION; private in state.ts so re-stated here. const CURRENT_INTERFACE_VERSION = 4; -// Recompute on-disk byte size for a NewInterface element so tests don't bake -// magic body byte counts. Layout matches Rust NewInterface::serialize_body +// Recompute on-disk byte size for a Interface element so tests don't bake +// magic body byte counts. Layout matches Rust Interface::serialize_body // (interface.rs:641-658): u16 size + u8 version (3-byte prefix) + // u8 status + (u32+len) name + 4*u8 + u64*2 + u16 + u8 + u16 + 5-byte ip_net + // u16 + u8 + (u32+34*N) flex_algo_node_segments. -function expectedNewInterfaceSize(ni: { +function expectedInterfaceSize(ni: { name: string; flexAlgoNodeSegments?: Array; }): number { @@ -207,10 +207,10 @@ describe("Device fixture", () => { expect(dev.dzPrefixes).toHaveLength(1); expect(formatNetworkV4(dev.dzPrefixes[0])).toBe("10.10.0.0/24"); - // Legacy slot is the V2 projection of new_interfaces (always V2 per #3653); + // Legacy slot is the V2 projection of interfaces (always V2 per #3653); // both entries carry version 1 and no FlexAlgoNodeSegments. - expect(dev.interfaces).toHaveLength(2); - const iface0 = dev.interfaces[0]; + expect(dev.deprecatedInterfaces).toHaveLength(2); + const iface0 = dev.deprecatedInterfaces[0]; expect(iface0.version).toBe(1); expect(iface0.status).toBe(3); expect(iface0.name).toBe("Loopback0"); @@ -224,7 +224,7 @@ describe("Device fixture", () => { expect(iface0.nodeSegmentIdx).toBe(100); expect(iface0.userTunnelEndpoint).toBe(false); - const iface1 = dev.interfaces[1]; + const iface1 = dev.deprecatedInterfaces[1]; expect(iface1.version).toBe(1); expect(iface1.status).toBe(3); expect(iface1.name).toBe("Ethernet1"); @@ -241,27 +241,27 @@ describe("Device fixture", () => { expect(iface1.nodeSegmentIdx).toBe(200); expect(iface1.userTunnelEndpoint).toBe(true); - // Trailing new_interfaces vec carries the full V4 NewInterface bodies. - expect(dev.newInterfaces).toHaveLength(2); - const ni0 = dev.newInterfaces[0]; + // Trailing interfaces vec carries the full V4 Interface bodies. + expect(dev.interfaces).toHaveLength(2); + const ni0 = dev.interfaces[0]; expect(ni0.version).toBe(CURRENT_INTERFACE_VERSION); expect(ni0.name).toBe("Loopback0"); expect(ni0.loopbackType).toBe(1); // Vpnv4 expect(ni0.flexAlgoNodeSegments).toHaveLength(1); expect(ni0.flexAlgoNodeSegments![0].nodeSegmentIdx).toBe(300); - expect(ni0.size).toBe(expectedNewInterfaceSize(ni0)); + expect(ni0.size).toBe(expectedInterfaceSize(ni0)); - const ni1 = dev.newInterfaces[1]; + const ni1 = dev.interfaces[1]; expect(ni1.version).toBe(CURRENT_INTERFACE_VERSION); expect(ni1.name).toBe("Ethernet1"); expect(ni1.userTunnelEndpoint).toBe(true); expect(ni1.flexAlgoNodeSegments).toEqual([]); - expect(ni1.size).toBe(expectedNewInterfaceSize(ni1)); + expect(ni1.size).toBe(expectedInterfaceSize(ni1)); }); }); // Pre-#3667 on-disk format: legacy `interfaces` vec only, no trailing -// `new_interfaces`. SDK rebuilds new_interfaces from the legacy vec, stamping +// `interfaces`. SDK rebuilds interfaces from the legacy vec, stamping // each entry with version=CURRENT_INTERFACE_VERSION and size=0. describe("Device legacy fixture", () => { test("deserialize", () => { @@ -270,24 +270,24 @@ describe("Device legacy fixture", () => { expect(meta.name).toBe("DeviceLegacy"); // Legacy slot mirrors the original V1+V2 hand-serialized shape. - expect(dev.interfaces).toHaveLength(2); - expect(dev.interfaces[0].version).toBe(0); // V1 - expect(dev.interfaces[0].name).toBe("Loopback0"); - expect(dev.interfaces[1].version).toBe(1); // V2 - expect(dev.interfaces[1].name).toBe("Ethernet1"); + expect(dev.deprecatedInterfaces).toHaveLength(2); + expect(dev.deprecatedInterfaces[0].version).toBe(0); // V1 + expect(dev.deprecatedInterfaces[0].name).toBe("Loopback0"); + expect(dev.deprecatedInterfaces[1].version).toBe(1); // V2 + expect(dev.deprecatedInterfaces[1].name).toBe("Ethernet1"); - // Rebuilt new_interfaces: same field values as the legacy entries, but + // Rebuilt interfaces: same field values as the legacy entries, but // stamped with the current schema version and zero on-disk size. - expect(dev.newInterfaces).toHaveLength(2); - for (const ni of dev.newInterfaces) { + expect(dev.interfaces).toHaveLength(2); + for (const ni of dev.interfaces) { expect(ni.version).toBe(CURRENT_INTERFACE_VERSION); expect(ni.size).toBe(0); expect(ni.flexAlgoNodeSegments).toEqual([]); } - expect(dev.newInterfaces[0].name).toBe("Loopback0"); - expect(dev.newInterfaces[0].loopbackType).toBe(1); // Vpnv4 - expect(dev.newInterfaces[1].name).toBe("Ethernet1"); - expect(dev.newInterfaces[1].userTunnelEndpoint).toBe(true); + expect(dev.interfaces[0].name).toBe("Loopback0"); + expect(dev.interfaces[0].loopbackType).toBe(1); // Vpnv4 + expect(dev.interfaces[1].name).toBe("Ethernet1"); + expect(dev.interfaces[1].userTunnelEndpoint).toBe(true); }); }); @@ -300,17 +300,17 @@ describe("Device future-version fixture", () => { const dev = deserializeDevice(data); expect(meta.name).toBe("DeviceFutureVersion"); - expect(dev.newInterfaces).toHaveLength(2); - const ni0 = dev.newInterfaces[0]; + expect(dev.interfaces).toHaveLength(2); + const ni0 = dev.interfaces[0]; expect(ni0.version).toBe(CURRENT_INTERFACE_VERSION); expect(ni0.name).toBe("Loopback0"); expect(ni0.flexAlgoNodeSegments).toHaveLength(1); // Doctored element: future version stamp + 8 trailing junk bytes the reader // skips via seek(start+size). - const ni1 = dev.newInterfaces[1]; + const ni1 = dev.interfaces[1]; expect(ni1.version).toBe(5); - expect(ni1.size).toBe(expectedNewInterfaceSize(ni1) + 8); + expect(ni1.size).toBe(expectedInterfaceSize(ni1) + 8); expect(ni1.name).toBe("Ethernet1"); expect(ni1.userTunnelEndpoint).toBe(true); }); diff --git a/sdk/serviceability/typescript/serviceability/tests/new_interface.test.ts b/sdk/serviceability/typescript/serviceability/tests/interface.test.ts similarity index 86% rename from sdk/serviceability/typescript/serviceability/tests/new_interface.test.ts rename to sdk/serviceability/typescript/serviceability/tests/interface.test.ts index 4af3969bfd..76b18716d8 100644 --- a/sdk/serviceability/typescript/serviceability/tests/new_interface.test.ts +++ b/sdk/serviceability/typescript/serviceability/tests/interface.test.ts @@ -1,7 +1,7 @@ /** - * Hand-built byte-vector tests for the size-prefixed NewInterface reader. + * Hand-built byte-vector tests for the size-prefixed Interface reader. * - * Each NewInterface element on the wire is (u16 size, u8 version, body), where + * Each Interface element on the wire is (u16 size, u8 version, body), where * size includes the 3-byte prefix. The Device account stores this vec * immediately after max_multicast_publishers. */ @@ -135,7 +135,7 @@ function buildDevice( return concat(...parts); } -describe("size-prefixed NewInterface", () => { +describe("size-prefixed Interface", () => { test("populated trailing vec", () => { // Cross-language framing assertion: empty-name body length is // 1+4+1+1+1+1+8+8+2+1+2+5+2+1+4 = 42, so size = 3 + 42 = 45. @@ -149,27 +149,27 @@ describe("size-prefixed NewInterface", () => { const raw = buildDevice(2, ["Eth1", "Lo0"], trailing); const dev = deserializeDevice(raw); + expect(dev.deprecatedInterfaces.length).toBe(2); expect(dev.interfaces.length).toBe(2); - expect(dev.newInterfaces.length).toBe(2); - expect(dev.newInterfaces[0]!.name).toBe("Eth1"); - expect(dev.newInterfaces[1]!.name).toBe("Lo0"); - expect(dev.newInterfaces[0]!.version).toBe(CURRENT_INTERFACE_VERSION); - for (const ni of dev.newInterfaces) { + expect(dev.interfaces[0]!.name).toBe("Eth1"); + expect(dev.interfaces[1]!.name).toBe("Lo0"); + expect(dev.interfaces[0]!.version).toBe(CURRENT_INTERFACE_VERSION); + for (const ni of dev.interfaces) { const expected = 3 + newInterfaceBody(ni.name).length; expect(ni.size).toBe(expected); } }); - test("legacy account rebuilds new_interfaces", () => { + test("legacy account rebuilds interfaces", () => { const raw = buildDevice(2, ["Eth1", "Lo0"], null); const dev = deserializeDevice(raw); + expect(dev.deprecatedInterfaces.length).toBe(2); expect(dev.interfaces.length).toBe(2); - expect(dev.newInterfaces.length).toBe(2); - expect(dev.newInterfaces[0]!.name).toBe("Eth1"); - expect(dev.newInterfaces[1]!.name).toBe("Lo0"); + expect(dev.interfaces[0]!.name).toBe("Eth1"); + expect(dev.interfaces[1]!.name).toBe("Lo0"); // Rebuilt entries are stamped with the current schema version and zero size. - for (const ni of dev.newInterfaces) { + for (const ni of dev.interfaces) { expect(ni.version).toBe(CURRENT_INTERFACE_VERSION); expect(ni.size).toBe(0); } @@ -180,7 +180,7 @@ describe("size-prefixed NewInterface", () => { const raw = buildDevice(2, ["Eth1", "Lo0"], trailing); expect(() => deserializeDevice(raw)).toThrow( - /length 1 != interfaces length 2/, + /interfaces length 1 != deprecatedInterfaces length 2/, ); }); @@ -196,9 +196,9 @@ describe("size-prefixed NewInterface", () => { const raw = buildDevice(1, ["Future1"], trailing); const dev = deserializeDevice(raw); - expect(dev.newInterfaces.length).toBe(1); - expect(dev.newInterfaces[0]!.version).toBe(5); - expect(dev.newInterfaces[0]!.size).toBe(3 + body.length); - expect(dev.newInterfaces[0]!.name).toBe("Future1"); + expect(dev.interfaces.length).toBe(1); + expect(dev.interfaces[0]!.version).toBe(5); + expect(dev.interfaces[0]!.size).toBe(3 + body.length); + expect(dev.interfaces[0]!.name).toBe("Future1"); }); }); diff --git a/smartcontract/cli/src/device/create.rs b/smartcontract/cli/src/device/create.rs index 70eaad2db2..f646f55160 100644 --- a/smartcontract/cli/src/device/create.rs +++ b/smartcontract/cli/src/device/create.rs @@ -344,7 +344,6 @@ mod tests { status: DeviceStatus::Activated, mgmt_vrf: String::default(), interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 100, owner: Pubkey::default(), @@ -358,6 +357,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mut devices = HashMap::new(); diff --git a/smartcontract/cli/src/device/delete.rs b/smartcontract/cli/src/device/delete.rs index faa5700d8d..64324a1b87 100644 --- a/smartcontract/cli/src/device/delete.rs +++ b/smartcontract/cli/src/device/delete.rs @@ -115,7 +115,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -128,6 +127,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/get.rs b/smartcontract/cli/src/device/get.rs index 2da7654be0..4b8cf8b5aa 100644 --- a/smartcontract/cli/src/device/get.rs +++ b/smartcontract/cli/src/device/get.rs @@ -6,7 +6,7 @@ use doublezero_sdk::{ contributor::get::GetContributorCommand, device::get::GetDeviceCommand, exchange::get::GetExchangeCommand, }, - GetLocationCommand, NewInterface, + GetLocationCommand, Interface, }; use serde::Serialize; use solana_sdk::pubkey::Pubkey; @@ -45,8 +45,8 @@ struct InterfaceDisplay { pub tunnel_endpoint: bool, } -impl From<&NewInterface> for InterfaceDisplay { - fn from(iface: &NewInterface) -> Self { +impl From<&Interface> for InterfaceDisplay { + fn from(iface: &Interface) -> Self { Self { name: iface.name.clone(), status: iface.status.to_string(), @@ -142,7 +142,7 @@ impl GetDeviceCliCommand { public_ip: device.public_ip.to_string(), dz_prefixes: device.dz_prefixes.to_string(), cyoa_ips: device - .new_interfaces + .interfaces .iter() .filter(|iface| iface.user_tunnel_endpoint) .map(|iface| iface.ip_net.to_string()) @@ -150,7 +150,7 @@ impl GetDeviceCliCommand { metrics_publisher: device.metrics_publisher_pk.to_string(), mgmt_vrf: device.mgmt_vrf, interfaces: device - .new_interfaces + .interfaces .iter() .map(InterfaceDisplay::from) .collect(), @@ -236,7 +236,6 @@ mod tests { owner: device1_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -249,6 +248,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let contributor = Contributor { diff --git a/smartcontract/cli/src/device/interface/create.rs b/smartcontract/cli/src/device/interface/create.rs index 008a33d2f9..b1e5f421af 100644 --- a/smartcontract/cli/src/device/interface/create.rs +++ b/smartcontract/cli/src/device/interface/create.rs @@ -67,7 +67,7 @@ impl CreateDeviceInterfaceCliCommand { .map_err(|_| eyre::eyre!("Device with pubkey/code '{}' not found", self.device))?; device - .new_interfaces + .interfaces .iter() .find(|i| i.name == self.name) .map_or(Ok(()), |_| { @@ -101,7 +101,7 @@ impl CreateDeviceInterfaceCliCommand { if dev.contributor_pk != device.contributor_pk { continue; } - for iface in &dev.new_interfaces { + for iface in &dev.interfaces { if iface.ip_net == *ip_net { eyre::bail!( "IP {} is already assigned to interface {} on device {}", @@ -144,8 +144,8 @@ mod tests { use super::*; use crate::tests::utils::create_test_client; use doublezero_sdk::{ - AccountType, CurrentInterfaceVersion, Device, DeviceStatus, DeviceType, InterfaceStatus, - InterfaceType, LoopbackType, + AccountType, Device, DeviceStatus, DeviceType, Interface, InterfaceStatus, InterfaceType, + LoopbackType, }; use doublezero_serviceability::state::interface::{InterfaceCYOA, InterfaceDIA, RoutingMode}; use mockall::predicate; @@ -176,7 +176,6 @@ mod tests { owner: device1_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -189,6 +188,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::new_unique(); @@ -208,8 +208,7 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: device2_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Activated, name: "Loopback100".to_string(), interface_type: InterfaceType::Loopback, @@ -224,9 +223,8 @@ mod tests { ip_net: "185.189.47.80/32".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap()], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -239,6 +237,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mut devices = HashMap::new(); @@ -308,7 +307,7 @@ mod tests { ), owner: device1_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Pending, name: "Ethernet0".to_string(), interface_type: InterfaceType::Physical, @@ -323,9 +322,8 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: true, - } - .to_interface()], - new_interfaces: vec![], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -338,6 +336,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -414,7 +413,6 @@ mod tests { owner: device1_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -427,6 +425,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; client @@ -486,7 +485,6 @@ mod tests { owner: device1_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -499,6 +497,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/interface/delete.rs b/smartcontract/cli/src/device/interface/delete.rs index 2db6dcd8a3..490d91c13d 100644 --- a/smartcontract/cli/src/device/interface/delete.rs +++ b/smartcontract/cli/src/device/interface/delete.rs @@ -63,7 +63,7 @@ mod tests { use super::*; use crate::tests::utils::create_test_client; use doublezero_program_common::types::NetworkV4List; - use doublezero_sdk::{AccountType, CurrentInterfaceVersion, Device, DeviceStatus}; + use doublezero_sdk::{AccountType, Device, DeviceStatus, Interface}; use doublezero_serviceability::state::interface::{ InterfaceCYOA, InterfaceStatus, InterfaceType, LoopbackType, RoutingMode, }; @@ -93,9 +93,8 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::default(), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![ - (&CurrentInterfaceVersion { + interfaces: vec![ + Interface { status: InterfaceStatus::Unlinked, name: "Ethernet0".to_string(), interface_type: InterfaceType::Physical, @@ -110,10 +109,9 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 12, user_tunnel_endpoint: true, - }) - .try_into() - .unwrap(), - (&CurrentInterfaceVersion { + ..Default::default() + }, + Interface { status: InterfaceStatus::Activated, name: "Loopback0".to_string(), interface_type: InterfaceType::Loopback, @@ -128,9 +126,8 @@ mod tests { ip_net: "10.0.1.1/24".parse().unwrap(), node_segment_idx: 13, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap(), + ..Default::default() + }, ], max_users: 255, users_count: 0, @@ -144,6 +141,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/interface/get.rs b/smartcontract/cli/src/device/interface/get.rs index 744745587e..8cbbfc4566 100644 --- a/smartcontract/cli/src/device/interface/get.rs +++ b/smartcontract/cli/src/device/interface/get.rs @@ -43,7 +43,7 @@ impl GetDeviceInterfaceCliCommand { })?; let interface = device - .new_interfaces + .interfaces .iter() .find(|i| i.name.to_lowercase() == self.name.to_lowercase()) .ok_or_else(|| eyre::eyre!("Interface '{}' not found", self.name))?; @@ -86,8 +86,8 @@ mod tests { device::interface::get::GetDeviceInterfaceCliCommand, tests::utils::create_test_client, }; use doublezero_sdk::{ - commands::device::get::GetDeviceCommand, AccountType, CurrentInterfaceVersion, Device, - DeviceStatus, DeviceType, + commands::device::get::GetDeviceCommand, AccountType, Device, DeviceStatus, DeviceType, + Interface, }; use doublezero_serviceability::state::interface::{ InterfaceStatus, InterfaceType, LoopbackType, @@ -120,8 +120,7 @@ mod tests { ), owner: device1_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Activated, name: "eth0".to_string(), interface_type: InterfaceType::Physical, @@ -136,9 +135,8 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 42, user_tunnel_endpoint: true, - }) - .try_into() - .unwrap()], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -151,6 +149,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/interface/list.rs b/smartcontract/cli/src/device/interface/list.rs index 79f6cef997..020c5d3b68 100644 --- a/smartcontract/cli/src/device/interface/list.rs +++ b/smartcontract/cli/src/device/interface/list.rs @@ -3,7 +3,7 @@ use clap::Args; use doublezero_program_common::types::NetworkV4; use doublezero_sdk::{ commands::device::{get::GetDeviceCommand, list::ListDeviceCommand}, - InterfaceType, NewInterface, + Interface, InterfaceType, }; use doublezero_serviceability::state::interface::{ InterfaceCYOA, InterfaceDIA, LoopbackType, RoutingMode, @@ -56,7 +56,7 @@ impl ListDeviceInterfaceCliCommand { .map_err(|_| eyre::eyre!("Device not found"))?; device - .new_interfaces + .interfaces .iter() .map(|iface| build_display(iface, &device.code)) .collect() @@ -67,7 +67,7 @@ impl ListDeviceInterfaceCliCommand { .iter() .flat_map(|(_, device)| { device - .new_interfaces + .interfaces .iter() .map(|iface| build_display(iface, &device.code)) }) @@ -90,7 +90,7 @@ impl ListDeviceInterfaceCliCommand { } } -fn build_display(iface: &NewInterface, device_code: &str) -> DeviceInterfaceDisplay { +fn build_display(iface: &Interface, device_code: &str) -> DeviceInterfaceDisplay { DeviceInterfaceDisplay { device: device_code.to_string(), name: iface.name.clone(), @@ -117,8 +117,8 @@ mod tests { }; use doublezero_sdk::{ - commands::device::get::GetDeviceCommand, AccountType, CurrentInterfaceVersion, Device, - DeviceStatus, DeviceType, + commands::device::get::GetDeviceCommand, AccountType, Device, DeviceStatus, DeviceType, + Interface, }; use doublezero_serviceability::state::interface::{ InterfaceStatus, InterfaceType, LoopbackType, @@ -147,9 +147,8 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![ - (&CurrentInterfaceVersion { + interfaces: vec![ + Interface { status: InterfaceStatus::Activated, name: "eth0".to_string(), interface_type: InterfaceType::Physical, @@ -165,10 +164,9 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 12, user_tunnel_endpoint: true, - }) - .try_into() - .unwrap(), - (&CurrentInterfaceVersion { + ..Default::default() + }, + Interface { status: InterfaceStatus::Activated, name: "lo0".to_string(), interface_type: InterfaceType::Loopback, @@ -184,9 +182,8 @@ mod tests { ip_net: "10.0.1.1/24".parse().unwrap(), node_segment_idx: 13, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap(), + ..Default::default() + }, ], max_users: 255, users_count: 0, @@ -200,6 +197,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/interface/update.rs b/smartcontract/cli/src/device/interface/update.rs index 8f27112e4f..830bce9087 100644 --- a/smartcontract/cli/src/device/interface/update.rs +++ b/smartcontract/cli/src/device/interface/update.rs @@ -139,7 +139,7 @@ impl UpdateDeviceInterfaceCliCommand { if dev.contributor_pk != device.contributor_pk { continue; } - for iface in &dev.new_interfaces { + for iface in &dev.interfaces { // Skip the interface being updated if *pk == device_pk && iface.name.eq_ignore_ascii_case(&self.name) { continue; @@ -194,7 +194,7 @@ impl UpdateDeviceInterfaceCliCommand { mod tests { use super::*; use crate::tests::utils::create_test_client; - use doublezero_sdk::{AccountType, CurrentInterfaceVersion, Device, DeviceStatus, DeviceType}; + use doublezero_sdk::{AccountType, Device, DeviceStatus, DeviceType, Interface}; use doublezero_serviceability::state::interface::{ InterfaceCYOA, InterfaceStatus, InterfaceType, LoopbackType, RoutingMode, }; @@ -225,9 +225,8 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::default(), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![ - (&CurrentInterfaceVersion { + interfaces: vec![ + Interface { status: InterfaceStatus::Activated, name: "Ethernet0".to_string(), interface_type: InterfaceType::Physical, @@ -242,10 +241,9 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: true, - }) - .try_into() - .unwrap(), - (&CurrentInterfaceVersion { + ..Default::default() + }, + Interface { status: InterfaceStatus::Activated, name: "Loopback0".to_string(), interface_type: InterfaceType::Loopback, @@ -260,9 +258,8 @@ mod tests { ip_net: "10.0.1.1/24".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap(), + ..Default::default() + }, ], max_users: 255, users_count: 0, @@ -276,6 +273,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -363,8 +361,7 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::default(), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Activated, name: "Loopback0".to_string(), interface_type: InterfaceType::Loopback, @@ -379,9 +376,8 @@ mod tests { ip_net: "10.0.0.1/32".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap()], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -394,6 +390,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::new_unique(); @@ -413,8 +410,7 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::default(), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Activated, name: "Loopback100".to_string(), interface_type: InterfaceType::Loopback, @@ -429,9 +425,8 @@ mod tests { ip_net: "185.189.47.80/32".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: false, - }) - .try_into() - .unwrap()], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -444,6 +439,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mut devices = HashMap::new(); @@ -511,8 +507,7 @@ mod tests { metrics_publisher_pk: Pubkey::default(), owner: Pubkey::default(), mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Activated, name: "Ethernet0".to_string(), interface_type: InterfaceType::Physical, @@ -527,9 +522,8 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: true, - }) - .try_into() - .unwrap()], + ..Default::default() + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -542,6 +536,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/device/list.rs b/smartcontract/cli/src/device/list.rs index 996754c197..e06334264f 100644 --- a/smartcontract/cli/src/device/list.rs +++ b/smartcontract/cli/src/device/list.rs @@ -226,7 +226,7 @@ impl ListDeviceCliCommand { status: device.status, dz_prefixes: device.dz_prefixes.clone(), cyoa_ips: device - .new_interfaces + .interfaces .iter() .filter(|iface| iface.user_tunnel_endpoint) .map(|iface| iface.ip_net.to_string()) @@ -368,7 +368,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -381,6 +380,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -512,7 +512,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -525,6 +524,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -545,7 +545,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -558,6 +557,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -674,7 +674,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -687,6 +686,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -707,7 +707,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -720,6 +719,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -836,7 +836,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -849,6 +848,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -869,7 +869,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::Pending, @@ -881,6 +880,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1014,7 +1014,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1027,6 +1026,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1047,7 +1047,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1060,6 +1059,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1199,7 +1199,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1212,6 +1211,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1232,7 +1232,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1245,6 +1244,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1380,7 +1380,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1393,6 +1392,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1413,7 +1413,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1426,6 +1425,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1559,7 +1559,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1572,6 +1571,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1592,7 +1592,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1605,6 +1604,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1721,7 +1721,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1734,6 +1733,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1754,7 +1754,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::Impaired, @@ -1767,6 +1766,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1883,7 +1883,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1896,6 +1895,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -1916,7 +1916,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1928,6 +1927,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -2044,7 +2044,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -2057,6 +2056,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -2496,7 +2496,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -2509,6 +2508,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"); @@ -2529,7 +2529,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPD"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -2542,6 +2541,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device3_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPF"); @@ -2562,7 +2562,6 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPF"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -2575,6 +2574,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { diff --git a/smartcontract/cli/src/device/sethealth.rs b/smartcontract/cli/src/device/sethealth.rs index 245facdee4..291cef8f6a 100644 --- a/smartcontract/cli/src/device/sethealth.rs +++ b/smartcontract/cli/src/device/sethealth.rs @@ -90,7 +90,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -103,6 +102,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2 = Device { account_type: AccountType::Device, @@ -121,7 +121,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -134,6 +133,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device3 = Device { account_type: AccountType::Device, @@ -152,7 +152,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -165,6 +164,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device_list = HashMap::from([ (pda_pubkey, device1.clone()), diff --git a/smartcontract/cli/src/device/update.rs b/smartcontract/cli/src/device/update.rs index 7348e9fc5f..b3122e2b06 100644 --- a/smartcontract/cli/src/device/update.rs +++ b/smartcontract/cli/src/device/update.rs @@ -186,7 +186,6 @@ impl UpdateDeviceCliCommand { None => None, }, mgmt_vrf: self.mgmt_vrf, - interfaces: None, max_users: self.max_users, users_count: self.users_count, status: self.status, @@ -261,7 +260,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -274,6 +272,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2 = Device { account_type: AccountType::Device, @@ -292,7 +291,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -305,6 +303,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device3 = Device { account_type: AccountType::Device, @@ -323,7 +322,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -336,6 +334,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device_list = HashMap::from([ (pda_pubkey, device1.clone()), @@ -376,7 +375,6 @@ mod tests { "HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx", )), mgmt_vrf: Some("default".to_string()), - interfaces: None, max_users: Some(1025), users_count: Some(0), status: None, @@ -454,7 +452,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -467,6 +464,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2 = Device { account_type: AccountType::Device, @@ -485,7 +483,6 @@ mod tests { owner: other_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -498,6 +495,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device_list = HashMap::from([(pda_pubkey, device1.clone()), (other_pubkey, device2)]); @@ -578,7 +576,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 1024, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -591,6 +588,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2 = Device { account_type: AccountType::Device, @@ -609,7 +607,6 @@ mod tests { owner: other_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 1024, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -622,6 +619,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device_list = HashMap::from([(pda_pubkey, device1.clone()), (other_pubkey, device2)]); diff --git a/smartcontract/cli/src/exchange/get.rs b/smartcontract/cli/src/exchange/get.rs index 07da23cf27..950baf61fe 100644 --- a/smartcontract/cli/src/exchange/get.rs +++ b/smartcontract/cli/src/exchange/get.rs @@ -134,7 +134,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: Vec::new(), - new_interfaces: Vec::new(), dz_prefixes: "10.0.0.1/24".parse().unwrap(), max_users: 255, users_count: 0, @@ -148,6 +147,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/exchange/list.rs b/smartcontract/cli/src/exchange/list.rs index 39962a4ce9..5ae526517e 100644 --- a/smartcontract/cli/src/exchange/list.rs +++ b/smartcontract/cli/src/exchange/list.rs @@ -130,7 +130,6 @@ mod tests { owner: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -143,6 +142,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::new_unique(); let device2 = Device { @@ -162,7 +162,6 @@ mod tests { owner: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -175,6 +174,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { diff --git a/smartcontract/cli/src/exchange/setdevice.rs b/smartcontract/cli/src/exchange/setdevice.rs index ed03fbbc21..8b6a879c86 100644 --- a/smartcontract/cli/src/exchange/setdevice.rs +++ b/smartcontract/cli/src/exchange/setdevice.rs @@ -102,7 +102,6 @@ mod tests { device_type: DeviceType::Hybrid, dz_prefixes: "10.0.0.1/31".parse().unwrap(), interfaces: Vec::new(), - new_interfaces: Vec::new(), mgmt_vrf: "".to_string(), public_ip: "100.0.0.1".parse().unwrap(), reference_count: 0, @@ -118,6 +117,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let exchange = Exchange { diff --git a/smartcontract/cli/src/link/accept.rs b/smartcontract/cli/src/link/accept.rs index 28d442468c..84cc57444a 100644 --- a/smartcontract/cli/src/link/accept.rs +++ b/smartcontract/cli/src/link/accept.rs @@ -47,7 +47,7 @@ impl AcceptLinkCliCommand { })?; let side_z_iface = device_z - .new_interfaces + .interfaces .iter() .find(|i| i.name.to_lowercase() == self.side_z_interface.to_lowercase()) .ok_or_else(|| { @@ -101,11 +101,9 @@ mod tests { link::{accept::AcceptLinkCommand, get::GetLinkCommand}, }, get_link_pda, AccountType, Contributor, ContributorStatus, Device, DeviceStatus, - DeviceType, InterfaceStatus, Link, LinkLinkType, LinkStatus, - }; - use doublezero_serviceability::state::interface::{ - CurrentInterfaceVersion, InterfaceType, LoopbackType, + DeviceType, Interface, InterfaceStatus, Link, LinkLinkType, LinkStatus, }; + use doublezero_serviceability::state::interface::{InterfaceType, LoopbackType}; use mockall::predicate; use solana_sdk::{pubkey::Pubkey, signature::Signature}; @@ -143,15 +141,12 @@ mod tests { exchange_pk: Pubkey::default(), code: "dev01".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { name: "Ethernet1/1".to_string(), status: InterfaceStatus::Unlinked, interface_type: InterfaceType::Physical, ..Default::default() - }) - .try_into() - .unwrap()], + }], device_type: DeviceType::Hybrid, public_ip: "127.0.0.1".parse().unwrap(), status: DeviceStatus::Activated, @@ -171,6 +166,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -188,8 +184,7 @@ mod tests { index: 2, reference_count: 0, code: "dev02".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/2".to_string(), interface_type: InterfaceType::Physical, @@ -199,9 +194,7 @@ mod tests { node_segment_idx: 0, user_tunnel_endpoint: false, ..Default::default() - }) - .try_into() - .unwrap()], + }], location_pk: Pubkey::default(), exchange_pk: Pubkey::default(), device_type: doublezero_sdk::DeviceType::Hybrid, @@ -223,6 +216,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/link/delete.rs b/smartcontract/cli/src/link/delete.rs index d72613e9f7..ba3f02ed65 100644 --- a/smartcontract/cli/src/link/delete.rs +++ b/smartcontract/cli/src/link/delete.rs @@ -82,7 +82,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -95,6 +94,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx"); let exchange2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkce"); @@ -116,7 +116,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -129,6 +128,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let tunnel = Link { diff --git a/smartcontract/cli/src/link/dzx_create.rs b/smartcontract/cli/src/link/dzx_create.rs index 8c4aef165c..0352a027e7 100644 --- a/smartcontract/cli/src/link/dzx_create.rs +++ b/smartcontract/cli/src/link/dzx_create.rs @@ -92,7 +92,7 @@ impl CreateDZXLinkCliCommand { .map_err(|_| eyre::eyre!("Device not found"))?; let side_a_iface = side_a_dev - .new_interfaces + .interfaces .iter() .find(|i| i.name.to_lowercase() == self.side_a_interface.to_lowercase()) .ok_or_else(|| { @@ -185,8 +185,8 @@ mod tests { device::get::GetDeviceCommand, link::{create::CreateLinkCommand, get::GetLinkCommand}, }, - get_device_pda, AccountType, CurrentInterfaceVersion, Device, DeviceStatus, DeviceType, - InterfaceStatus, Link, LinkLinkType, LinkStatus, + get_device_pda, AccountType, Device, DeviceStatus, DeviceType, Interface, InterfaceStatus, + Link, LinkLinkType, LinkStatus, }; use doublezero_serviceability::state::interface::{InterfaceCYOA, InterfaceType, LoopbackType}; use mockall::predicate; @@ -224,8 +224,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -236,9 +235,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -251,6 +248,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx"); let exchange2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkce"); @@ -271,8 +269,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/2".to_string(), interface_type: InterfaceType::Physical, @@ -283,9 +280,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -298,6 +293,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location3_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquCkcx"); let exchange3_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquCkce"); @@ -318,8 +314,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/3".to_string(), interface_type: InterfaceType::Physical, @@ -330,9 +325,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -345,6 +338,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let link = Link { account_type: AccountType::Link, @@ -498,8 +492,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -510,9 +503,7 @@ mod tests { user_tunnel_endpoint: true, interface_cyoa: InterfaceCYOA::GREOverDIA, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -525,6 +516,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcf"); @@ -601,8 +593,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -613,9 +604,7 @@ mod tests { node_segment_idx: 0, user_tunnel_endpoint: true, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -628,6 +617,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; let device2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcf"); diff --git a/smartcontract/cli/src/link/get.rs b/smartcontract/cli/src/link/get.rs index 0a432a8d57..994bb36ea1 100644 --- a/smartcontract/cli/src/link/get.rs +++ b/smartcontract/cli/src/link/get.rs @@ -202,7 +202,6 @@ mod tests { owner: device1_pk, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -215,6 +214,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2 = Device { code: "side-z-device".to_string(), diff --git a/smartcontract/cli/src/link/list.rs b/smartcontract/cli/src/link/list.rs index d47ee68114..525ab7f62c 100644 --- a/smartcontract/cli/src/link/list.rs +++ b/smartcontract/cli/src/link/list.rs @@ -330,7 +330,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -343,6 +342,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"); let device2 = Device { @@ -362,7 +362,6 @@ mod tests { metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -375,6 +374,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -534,7 +534,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -547,6 +546,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::new_unique(); let device2 = Device { @@ -566,7 +566,6 @@ mod tests { metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -579,6 +578,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -717,7 +717,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -730,6 +729,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzoa"); @@ -750,7 +750,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -763,6 +762,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -902,7 +902,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -915,6 +914,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzoa"); @@ -935,7 +935,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -948,6 +947,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { @@ -1087,7 +1087,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -1100,6 +1099,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { diff --git a/smartcontract/cli/src/link/wan_create.rs b/smartcontract/cli/src/link/wan_create.rs index 63bd665eb2..4a87a76468 100644 --- a/smartcontract/cli/src/link/wan_create.rs +++ b/smartcontract/cli/src/link/wan_create.rs @@ -95,7 +95,7 @@ impl CreateWANLinkCliCommand { .map_err(|_| eyre::eyre!("Device not found"))?; let side_a_iface = side_a_dev - .new_interfaces + .interfaces .iter() .find(|i| i.name.to_lowercase() == self.side_a_interface.to_lowercase()) .ok_or_else(|| { @@ -136,7 +136,7 @@ impl CreateWANLinkCliCommand { } let side_z_iface = side_z_dev - .new_interfaces + .interfaces .iter() .find(|i| i.name.to_lowercase() == self.side_z_interface.to_lowercase()) .ok_or_else(|| { @@ -229,8 +229,8 @@ mod tests { device::get::GetDeviceCommand, link::{create::CreateLinkCommand, get::GetLinkCommand}, }, - get_device_pda, AccountType, CurrentInterfaceVersion, Device, DeviceStatus, DeviceType, - Link, LinkLinkType, LinkStatus, + get_device_pda, AccountType, Device, DeviceStatus, DeviceType, Interface, Link, + LinkLinkType, LinkStatus, }; use doublezero_serviceability::state::interface::{ InterfaceCYOA, InterfaceStatus, InterfaceType, LoopbackType, @@ -270,8 +270,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -282,9 +281,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -297,6 +294,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx"); let exchange2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkce"); @@ -317,8 +315,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/2".to_string(), interface_type: InterfaceType::Physical, @@ -329,9 +326,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -344,6 +339,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location3_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquCkcx"); let exchange3_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquCkce"); @@ -364,8 +360,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/3".to_string(), interface_type: InterfaceType::Physical, @@ -376,9 +371,7 @@ mod tests { user_tunnel_endpoint: true, mtu: 9000, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -391,6 +384,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let link = Link { account_type: AccountType::Link, @@ -546,8 +540,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -558,9 +551,7 @@ mod tests { user_tunnel_endpoint: true, interface_cyoa: InterfaceCYOA::GREOverDIA, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -573,6 +564,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let location2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx"); let exchange2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkce"); @@ -593,8 +585,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/2".to_string(), interface_type: InterfaceType::Physical, @@ -604,9 +595,7 @@ mod tests { node_segment_idx: 0, user_tunnel_endpoint: true, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -619,6 +608,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -686,8 +676,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/1".to_string(), interface_type: InterfaceType::Physical, @@ -698,9 +687,7 @@ mod tests { node_segment_idx: 0, user_tunnel_endpoint: true, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -713,6 +700,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; let device2_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcf"); let device2 = Device { @@ -731,8 +719,7 @@ mod tests { status: DeviceStatus::Activated, owner: pda_pubkey, mgmt_vrf: "default".to_string(), - interfaces: vec![], - new_interfaces: vec![(&CurrentInterfaceVersion { + interfaces: vec![Interface { status: InterfaceStatus::Unlinked, name: "Ethernet1/2".to_string(), interface_type: InterfaceType::Physical, @@ -743,9 +730,7 @@ mod tests { node_segment_idx: 0, user_tunnel_endpoint: true, ..Default::default() - }) - .try_into() - .unwrap()], + }], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -758,6 +743,7 @@ mod tests { max_multicast_subscribers: 0, max_multicast_publishers: 0, reserved_seats: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/multicastgroup/get.rs b/smartcontract/cli/src/multicastgroup/get.rs index 8f959fee06..20ed0ccf94 100644 --- a/smartcontract/cli/src/multicastgroup/get.rs +++ b/smartcontract/cli/src/multicastgroup/get.rs @@ -288,7 +288,6 @@ mod tests { metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -301,6 +300,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let cloned_device = device.clone(); diff --git a/smartcontract/cli/src/multicastgroup/list.rs b/smartcontract/cli/src/multicastgroup/list.rs index 38b0e0c01b..c98638491a 100644 --- a/smartcontract/cli/src/multicastgroup/list.rs +++ b/smartcontract/cli/src/multicastgroup/list.rs @@ -111,7 +111,6 @@ mod tests { owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -124,6 +123,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"); let device2 = Device { @@ -143,7 +143,6 @@ mod tests { owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -156,6 +155,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client.expect_list_device().returning(move |_| { diff --git a/smartcontract/cli/src/poll_for_activation.rs b/smartcontract/cli/src/poll_for_activation.rs index 01dea59668..ded3db9247 100644 --- a/smartcontract/cli/src/poll_for_activation.rs +++ b/smartcontract/cli/src/poll_for_activation.rs @@ -3,8 +3,8 @@ use doublezero_sdk::{ device::get::GetDeviceCommand, link::get::GetLinkCommand, multicastgroup::get::GetMulticastGroupCommand, user::get::GetUserCommand, }, - Device, DeviceStatus, Link, LinkStatus, MulticastGroup, MulticastGroupStatus, NewInterface, - User, UserStatus, + Device, DeviceStatus, Interface, Link, LinkStatus, MulticastGroup, MulticastGroupStatus, User, + UserStatus, }; use solana_sdk::pubkey::Pubkey; @@ -61,7 +61,7 @@ pub fn poll_for_device_interface_activated( client: &dyn CliCommand, device_pubkey: &Pubkey, interface_name: &str, -) -> eyre::Result { +) -> eyre::Result { let start_time = std::time::Instant::now(); let timeout = std::time::Duration::from_secs(20); let poll_interval = std::time::Duration::from_secs(1); @@ -83,7 +83,7 @@ pub fn poll_for_device_interface_activated( }) { Ok((_, device)) => { if let Some(iface) = device - .new_interfaces + .interfaces .iter() .find(|iface| iface.name.to_lowercase() == interface_name.to_lowercase()) { diff --git a/smartcontract/cli/src/resource/verify.rs b/smartcontract/cli/src/resource/verify.rs index 805e54fc2e..01f18019f0 100644 --- a/smartcontract/cli/src/resource/verify.rs +++ b/smartcontract/cli/src/resource/verify.rs @@ -903,7 +903,7 @@ fn verify_device_tunnel_block( // Check device loopback interfaces (only vpnv4/ipv4 loopback types) for (device_pk, device) in devices { - for iface in &device.new_interfaces { + for iface in &device.interfaces { if iface.interface_type == InterfaceType::Loopback && (iface.loopback_type == LoopbackType::Vpnv4 || iface.loopback_type == LoopbackType::Ipv4) @@ -978,7 +978,7 @@ fn verify_segment_routing_ids( let mut in_use: HashMap = HashMap::new(); for (device_pk, device) in devices { - for iface in &device.new_interfaces { + for iface in &device.interfaces { // Only check vpnv4/ipv4 loopbacks, and node_segment_idx == 0 means not allocated if iface.interface_type == InterfaceType::Loopback && (iface.loopback_type == LoopbackType::Vpnv4 diff --git a/smartcontract/cli/src/user/create.rs b/smartcontract/cli/src/user/create.rs index 268b6c7182..779e17d2fd 100644 --- a/smartcontract/cli/src/user/create.rs +++ b/smartcontract/cli/src/user/create.rs @@ -167,7 +167,6 @@ mod tests { metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -180,6 +179,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/user/create_subscribe.rs b/smartcontract/cli/src/user/create_subscribe.rs index 08d3784a2f..673116db3e 100644 --- a/smartcontract/cli/src/user/create_subscribe.rs +++ b/smartcontract/cli/src/user/create_subscribe.rs @@ -183,7 +183,6 @@ mod tests { status: DeviceStatus::Activated, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -196,6 +195,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/cli/src/user/get.rs b/smartcontract/cli/src/user/get.rs index d9896d3a20..bf338b89bf 100644 --- a/smartcontract/cli/src/user/get.rs +++ b/smartcontract/cli/src/user/get.rs @@ -217,7 +217,6 @@ mod tests { contributor_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], reference_count: 0, users_count: 0, max_users: 1000, @@ -230,6 +229,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mgroup_pubkey = Pubkey::new_unique(); @@ -411,7 +411,6 @@ mod tests { contributor_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], reference_count: 0, users_count: 0, max_users: 1000, @@ -424,6 +423,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let user = User { diff --git a/smartcontract/cli/src/user/list.rs b/smartcontract/cli/src/user/list.rs index 4a64e53112..db5fb93538 100644 --- a/smartcontract/cli/src/user/list.rs +++ b/smartcontract/cli/src/user/list.rs @@ -677,7 +677,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -690,6 +689,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let device2_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo8"); let device2 = Device { @@ -709,7 +709,6 @@ mod tests { metrics_publisher_pk: Pubkey::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: doublezero_serviceability::state::device::DeviceHealth::ReadyForUsers, @@ -722,6 +721,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mgroup1_pubkey = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo8"); let mgroup1 = MulticastGroup { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/device/create.rs b/smartcontract/programs/doublezero-serviceability/src/processors/device/create.rs index 9d1510358f..0de26d76a0 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/device/create.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/device/create.rs @@ -186,7 +186,6 @@ pub fn process_create_device( status: DeviceStatus::Pending, mgmt_vrf: value.mgmt_vrf.clone(), interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 0, // Initially, the Device is locked and must be activated by modifying the maximum number of users. // TODO: This line show be change when the health oracle is implemented @@ -202,6 +201,7 @@ pub fn process_create_device( reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, // Initially locked, must be set via device update + ..Default::default() }; // Atomic create+activate with onchain resource allocation diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/create.rs b/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/create.rs index 3745170576..7488559b4a 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/create.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/create.rs @@ -14,9 +14,8 @@ use crate::{ feature_flags::{is_feature_enabled, FeatureFlag}, globalstate::GlobalState, interface::{ - InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, LoopbackType, - NewInterface, RoutingMode, CURRENT_INTERFACE_SCHEMA_VERSION, CYOA_DIA_INTERFACE_MTU, - INTERFACE_MTU, + Interface, InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, LoopbackType, + RoutingMode, CURRENT_INTERFACE_SCHEMA_VERSION, CYOA_DIA_INTERFACE_MTU, INTERFACE_MTU, }, }, }; @@ -225,10 +224,10 @@ pub fn process_create_device_interface( } } - // size is intentionally left at 0 — the NewInterface serializer derives the + // size is intentionally left at 0 — the Interface serializer derives the // on-disk size fresh from the body bytes and ignores this field. It only // gets populated on deserialize, from the wire prefix. - device.push_interface(NewInterface { + device.push_interface(Interface { size: 0, version: CURRENT_INTERFACE_SCHEMA_VERSION, status, diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/update.rs b/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/update.rs index c9be18ef82..f017f5a441 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/update.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/device/interface/update.rs @@ -137,7 +137,7 @@ pub fn process_update_device_interface( let (idx, _) = device .find_interface(&value.name) .map_err(|_| DoubleZeroError::InterfaceNotFound)?; - let mut iface = device.new_interfaces[idx].clone(); + let mut iface = device.interfaces[idx].clone(); if let Some(loopback_type) = &value.loopback_type { if *loopback_type == LoopbackType::None { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/link/accept.rs b/smartcontract/programs/doublezero-serviceability/src/processors/link/accept.rs index ed2f503fd7..de9964171d 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/link/accept.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/link/accept.rs @@ -114,7 +114,7 @@ pub fn process_accept_link( } if !side_z_dev - .new_interfaces + .interfaces .iter() .any(|iface| iface.name == value.side_z_iface_name) { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/link/create.rs b/smartcontract/programs/doublezero-serviceability/src/processors/link/create.rs index 4449b49bcb..2677446387 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/link/create.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/link/create.rs @@ -152,7 +152,7 @@ pub fn process_create_link( } let side_a_iface = side_a_dev - .new_interfaces + .interfaces .iter() .find(|iface| iface.name == value.side_a_iface_name) .ok_or_else(|| { @@ -170,7 +170,7 @@ pub fn process_create_link( let side_z_iface_name = value.side_z_iface_name.clone().unwrap_or_default(); if let Some(ref z_name) = value.side_z_iface_name { let side_z_iface = side_z_dev - .new_interfaces + .interfaces .iter() .find(|iface| iface.name == *z_name) .ok_or_else(|| { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/topology/backfill.rs b/smartcontract/programs/doublezero-serviceability/src/processors/topology/backfill.rs index 76f18171b4..d4b27b4226 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/topology/backfill.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/topology/backfill.rs @@ -123,12 +123,12 @@ pub fn process_topology_backfill( msg!("BackfillTopology: processing device {}", device_account.key); let mut device = Device::try_from(&device_account.data.borrow()[..])?; let mut modified = false; - // `new_interfaces` is the source of truth for `flex_algo_node_segments`. - // The custom Device serializer projects `new_interfaces` to the legacy + // `interfaces` is the source of truth for `flex_algo_node_segments`. + // The custom Device serializer projects `interfaces` to the legacy // on-disk slot as V2, which intentionally drops segments — so we don't // mirror the change into the legacy in-memory vec here. - for idx in 0..device.new_interfaces.len() { - let new_iface = &device.new_interfaces[idx]; + for idx in 0..device.interfaces.len() { + let new_iface = &device.interfaces[idx]; if new_iface.loopback_type != LoopbackType::Vpnv4 { continue; } @@ -148,9 +148,7 @@ pub fn process_topology_backfill( topology: *topology_key, node_segment_idx, }; - device.new_interfaces[idx] - .flex_algo_node_segments - .push(segment); + device.interfaces[idx].flex_algo_node_segments.push(segment); modified = true; backfilled_count += 1; } diff --git a/smartcontract/programs/doublezero-serviceability/src/state/device.rs b/smartcontract/programs/doublezero-serviceability/src/state/device.rs index cf709c763a..8ff04f988c 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/device.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/device.rs @@ -3,7 +3,7 @@ use crate::{ helper::is_global, state::{ accounttype::AccountType, - interface::{CurrentInterfaceVersion, Interface, InterfaceV2, NewInterface}, + interface::{Interface, InterfaceDeprecated, InterfaceV2}, user::UserType, }, }; @@ -269,7 +269,7 @@ pub struct Device { )] pub contributor_pk: Pubkey, // 32 pub mgmt_vrf: String, // 4 + len - pub interfaces: Vec, // 4 + (14 + len(name)) * len + pub deprecated_interfaces: Vec, // 4 + (14 + len(name)) * len pub reference_count: u32, // 4 pub users_count: u16, // 2 pub max_users: u16, // 2 @@ -284,9 +284,9 @@ pub struct Device { pub max_multicast_publishers: u16, // 2 /// Forward-compatible interface vec written at the end of the on-disk layout. /// Legacy `interfaces` stays at its existing offset and is projected from - /// `new_interfaces` (always as `Interface::V2`) by the custom `BorshSerialize` + /// `interfaces` (always as `InterfaceDeprecated::V2`) by the custom `BorshSerialize` /// impl, keeping older readers byte-compatible. - pub new_interfaces: Vec, + pub interfaces: Vec, } impl Default for Device { @@ -306,7 +306,7 @@ impl Default for Device { metrics_publisher_pk: Pubkey::default(), contributor_pk: Pubkey::default(), mgmt_vrf: String::new(), - interfaces: Vec::new(), + deprecated_interfaces: Vec::new(), reference_count: 0, users_count: 0, max_users: 0, @@ -319,57 +319,41 @@ impl Default for Device { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, - new_interfaces: Vec::new(), + interfaces: Vec::new(), } } } impl Device { - pub fn find_interface(&self, name: &str) -> Result<(usize, &NewInterface), String> { - self.new_interfaces - .iter() - .enumerate() - .find(|(_, iface)| iface.name.eq_ignore_ascii_case(name)) - .ok_or_else(|| format!("Interface with name '{name}' not found")) - } - - /// Temporary helper that returns a `CurrentInterfaceVersion` projection of the - /// matched interface. Retained for CLI callers during the staged migration; - /// once those callers move to `find_interface` (which returns `&NewInterface`), - /// this helper is removed. No `processors/` code calls this any more. - pub fn find_interface_legacy( - &self, - name: &str, - ) -> Result<(usize, CurrentInterfaceVersion), String> { + pub fn find_interface(&self, name: &str) -> Result<(usize, &Interface), String> { self.interfaces .iter() - .map(|iface| iface.into_current_version()) .enumerate() .find(|(_, iface)| iface.name.eq_ignore_ascii_case(name)) .ok_or_else(|| format!("Interface with name '{name}' not found")) } /// Replaces the interface at `idx` in both legacy `interfaces` and - /// `new_interfaces`, keeping the two vecs in sync. The custom `BorshSerialize` - /// projects the on-disk legacy slot from `new_interfaces`, so callers that + /// `interfaces`, keeping the two vecs in sync. The custom `BorshSerialize` + /// projects the on-disk legacy slot from `interfaces`, so callers that /// only mutated `interfaces[idx]` would lose their change on save. - pub fn replace_interface(&mut self, idx: usize, iface: NewInterface) { - self.interfaces[idx] = InterfaceV2::from(&iface).to_interface(); - self.new_interfaces[idx] = iface; + pub fn replace_interface(&mut self, idx: usize, iface: Interface) { + self.deprecated_interfaces[idx] = InterfaceV2::from(&iface).to_interface(); + self.interfaces[idx] = iface; } - /// Appends an interface to both `interfaces` and `new_interfaces`. Same + /// Appends an interface to both `interfaces` and `interfaces`. Same /// rationale as `replace_interface`. - pub fn push_interface(&mut self, iface: NewInterface) { - self.interfaces + pub fn push_interface(&mut self, iface: Interface) { + self.deprecated_interfaces .push(InterfaceV2::from(&iface).to_interface()); - self.new_interfaces.push(iface); + self.interfaces.push(iface); } - /// Removes the interface at `idx` from both `interfaces` and `new_interfaces`. + /// Removes the interface at `idx` from both `interfaces` and `interfaces`. pub fn remove_interface(&mut self, idx: usize) { + self.deprecated_interfaces.remove(idx); self.interfaces.remove(idx); - self.new_interfaces.remove(idx); } pub fn is_device_eligible_for_provisioning(&self) -> bool { @@ -524,18 +508,18 @@ impl fmt::Display for Device { impl borsh::BorshSerialize for Device { fn serialize(&self, writer: &mut W) -> borsh::io::Result<()> { - // Project the legacy on-disk vec from `new_interfaces`. Always V2 to match the - // post-#3653 default; older readers see a normal `Vec` at the - // existing offset and don't observe the trailing `new_interfaces` vec. - let legacy: Vec = self - .new_interfaces + // Project the legacy on-disk vec from `interfaces`. Always V2 to match the + // post-#3653 default; older readers see a normal `Vec` at the + // existing offset and don't observe the trailing `interfaces` vec. + let legacy: Vec = self + .interfaces .iter() - .map(|n| Interface::V2(InterfaceV2::from(n))) + .map(|n| InterfaceDeprecated::V2(InterfaceV2::from(n))) .collect(); assert_eq!( legacy.len(), - self.new_interfaces.len(), - "legacy projection length must match new_interfaces length" + self.interfaces.len(), + "legacy projection length must match interfaces length" ); self.account_type.serialize(writer)?; @@ -565,7 +549,7 @@ impl borsh::BorshSerialize for Device { self.reserved_seats.serialize(writer)?; self.multicast_publishers_count.serialize(writer)?; self.max_multicast_publishers.serialize(writer)?; - self.new_interfaces.serialize(writer)?; + self.interfaces.serialize(writer)?; Ok(()) } } @@ -592,7 +576,7 @@ impl TryFrom<&[u8]> for Device { BorshDeserialize::deserialize(&mut data).unwrap_or_default(); let contributor_pk: Pubkey = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); let mgmt_vrf: String = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); - let interfaces: Vec = + let deprecated_interfaces: Vec = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); let reference_count: u32 = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); let users_count: u16 = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); @@ -615,20 +599,19 @@ impl TryFrom<&[u8]> for Device { // Trailing forward-compat vec: present on accounts written by the current // serializer, absent on legacy accounts. - let trailing: Vec = - BorshDeserialize::deserialize(&mut data).unwrap_or_default(); + let trailing: Vec = BorshDeserialize::deserialize(&mut data).unwrap_or_default(); - let new_interfaces = if trailing.is_empty() { + let interfaces = if trailing.is_empty() { // Legacy account: rebuild from the legacy enum vec via per-variant // `TryFrom`. V3 is projected through V2, dropping `flex_algo_node_segments` // (V3 only exists from migrate/backfill paths post-#3653). - interfaces + deprecated_interfaces .iter() - .map(|iface| -> Result { + .map(|iface| -> Result { match iface { - Interface::V1(v1) => v1.try_into(), - Interface::V2(v2) => v2.try_into(), - Interface::V3(v3) => { + InterfaceDeprecated::V1(v1) => v1.try_into(), + InterfaceDeprecated::V2(v2) => v2.try_into(), + InterfaceDeprecated::V3(v3) => { let v2: InterfaceV2 = v3.try_into()?; (&v2).try_into() } @@ -638,8 +621,8 @@ impl TryFrom<&[u8]> for Device { } else { assert_eq!( trailing.len(), - interfaces.len(), - "trailing new_interfaces vec length must match legacy interfaces vec length" + deprecated_interfaces.len(), + "trailing interfaces vec length must match legacy interfaces vec length" ); trailing }; @@ -659,7 +642,7 @@ impl TryFrom<&[u8]> for Device { metrics_publisher_pk, contributor_pk, mgmt_vrf, - interfaces, + deprecated_interfaces, reference_count, users_count, max_users, @@ -672,7 +655,7 @@ impl TryFrom<&[u8]> for Device { reserved_seats, multicast_publishers_count, max_multicast_publishers, - new_interfaces, + interfaces, }; if out.account_type != AccountType::Device { @@ -832,7 +815,7 @@ mod tests { assert_eq!(val.metrics_publisher_pk, Pubkey::default()); assert_eq!(val.contributor_pk, Pubkey::default()); assert_eq!(val.mgmt_vrf, ""); - assert_eq!(val.interfaces.len(), 0); + assert_eq!(val.deprecated_interfaces.len(), 0); assert_eq!(val.reference_count, 0); assert_eq!(val.users_count, 0); assert_eq!(val.max_users, 0); @@ -856,8 +839,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -892,8 +875,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -928,8 +911,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -964,8 +947,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -1001,8 +984,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -1037,8 +1020,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -1073,8 +1056,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 0, device_health: DeviceHealth::ReadyForUsers, @@ -1111,8 +1094,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 6, max_users: 5, device_health: DeviceHealth::ReadyForUsers, @@ -1147,8 +1130,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, @@ -1168,7 +1151,7 @@ mod tests { #[test] fn test_state_device_validate_error_invalid_interface() { - let invalid_iface = CurrentInterfaceVersion { + let invalid_iface = Interface { status: InterfaceStatus::Activated, name: "".to_string(), // Invalid Name interface_type: InterfaceType::Physical, @@ -1183,8 +1166,8 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 42, user_tunnel_endpoint: true, - } - .to_interface(); + ..Default::default() + }; let val = Device { account_type: AccountType::Device, owner: Pubkey::new_unique(), @@ -1201,19 +1184,12 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), - new_interfaces: vec![(&invalid_iface.into_current_version()).try_into().unwrap()], interfaces: vec![invalid_iface], users_count: 1, max_users: 2, device_health: DeviceHealth::ReadyForUsers, desired_status: DeviceDesiredStatus::Pending, - unicast_users_count: 0, - multicast_subscribers_count: 0, - max_unicast_users: 0, - max_multicast_subscribers: 0, - reserved_seats: 0, - multicast_publishers_count: 0, - max_multicast_publishers: 0, + ..Default::default() }; let err = val.validate(); assert!(err.is_err()); @@ -1222,7 +1198,7 @@ mod tests { #[test] fn test_state_device_serialization() { - let iface_a = CurrentInterfaceVersion { + let mut iface_a = Interface { status: InterfaceStatus::Activated, name: "Switch1/1/1".to_string(), interface_type: InterfaceType::Physical, @@ -1237,8 +1213,10 @@ mod tests { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 42, user_tunnel_endpoint: true, + ..Default::default() }; - let iface_b = CurrentInterfaceVersion { + iface_a.size = iface_a.compute_on_disk_size().unwrap(); + let mut iface_b = Interface { status: InterfaceStatus::Deleting, name: "Switch1/1/2".to_string(), interface_type: InterfaceType::Physical, @@ -1253,7 +1231,9 @@ mod tests { ip_net: "10.0.1.1/24".parse().unwrap(), node_segment_idx: 24, user_tunnel_endpoint: false, + ..Default::default() }; + iface_b.size = iface_b.compute_on_disk_size().unwrap(); let val = Device { account_type: AccountType::Device, @@ -1271,22 +1251,12 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), - interfaces: vec![iface_a.to_interface(), iface_b.to_interface()], - new_interfaces: vec![ - (&iface_a).try_into().unwrap(), - (&iface_b).try_into().unwrap(), - ], + interfaces: vec![iface_a, iface_b], users_count: 111, max_users: 222, device_health: DeviceHealth::ReadyForUsers, desired_status: DeviceDesiredStatus::Pending, - unicast_users_count: 0, - multicast_subscribers_count: 0, - max_unicast_users: 0, - max_multicast_subscribers: 0, - reserved_seats: 0, - multicast_publishers_count: 0, - max_multicast_publishers: 0, + ..Default::default() }; let data = borsh::to_vec(&val).unwrap(); @@ -1345,8 +1315,8 @@ mod tests { status: DeviceStatus::Activated, metrics_publisher_pk: Pubkey::new_unique(), mgmt_vrf: "".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 0, device_health: DeviceHealth::Pending, @@ -1395,8 +1365,8 @@ mod test_device_validate { metrics_publisher_pk: Pubkey::new_unique(), contributor_pk: Pubkey::new_unique(), mgmt_vrf: "vrf1".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], reference_count: 0, users_count: 0, max_users: 10, @@ -1434,8 +1404,8 @@ mod test_device_validate_errors { metrics_publisher_pk: Pubkey::new_unique(), contributor_pk: Pubkey::new_unique(), mgmt_vrf: "vrf1".to_string(), + deprecated_interfaces: vec![], interfaces: vec![], - new_interfaces: vec![], reference_count: 0, users_count: 0, max_users: 10, @@ -1660,7 +1630,7 @@ mod test_device_validate_errors { } #[cfg(test)] -mod test_device_new_interfaces_vec { +mod test_device_interfaces_vec { use super::*; use crate::state::interface::{ InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, LoopbackType, RoutingMode, @@ -1668,8 +1638,8 @@ mod test_device_new_interfaces_vec { }; use borsh::BorshSerialize; - fn sample_iface(name: &str, vlan_id: u16) -> CurrentInterfaceVersion { - CurrentInterfaceVersion { + fn sample_iface(name: &str, vlan_id: u16) -> Interface { + let mut iface = Interface { status: InterfaceStatus::Activated, name: name.to_string(), interface_type: InterfaceType::Physical, @@ -1684,18 +1654,16 @@ mod test_device_new_interfaces_vec { ip_net: "10.0.0.1/24".parse().unwrap(), node_segment_idx: 0, user_tunnel_endpoint: false, - } + ..Default::default() + }; + iface.size = iface.compute_on_disk_size().unwrap(); + iface } fn sample_device_with_n_interfaces(n: usize) -> Device { - let v2s: Vec = (0..n) + let interfaces: Vec = (0..n) .map(|i| sample_iface(&format!("Switch1/1/{i}"), 100 + i as u16)) .collect(); - let interfaces = v2s.iter().map(|v| v.to_interface()).collect(); - let new_interfaces = v2s - .iter() - .map(|v| (v).try_into().unwrap()) - .collect::>(); Device { account_type: AccountType::Device, owner: Pubkey::new_unique(), @@ -1712,19 +1680,7 @@ mod test_device_new_interfaces_vec { contributor_pk: Pubkey::new_unique(), mgmt_vrf: "default".to_string(), interfaces, - reference_count: 0, - users_count: 0, - max_users: 0, - device_health: DeviceHealth::ReadyForUsers, - desired_status: DeviceDesiredStatus::Activated, - unicast_users_count: 0, - multicast_subscribers_count: 0, - max_unicast_users: 0, - max_multicast_subscribers: 0, - reserved_seats: 0, - multicast_publishers_count: 0, - max_multicast_publishers: 0, - new_interfaces, + ..Default::default() } } @@ -1736,30 +1692,30 @@ mod test_device_new_interfaces_vec { let bytes = borsh::to_vec(&device).unwrap(); let decoded = Device::try_from(&bytes[..]).unwrap(); + assert_eq!(decoded.deprecated_interfaces.len(), n); assert_eq!(decoded.interfaces.len(), n); - assert_eq!(decoded.new_interfaces.len(), n); for (i, (legacy, new)) in decoded - .interfaces + .deprecated_interfaces .iter() - .zip(decoded.new_interfaces.iter()) + .zip(decoded.interfaces.iter()) .enumerate() { - // Legacy is always the V2 projection of new_interfaces. - let legacy_v2 = legacy.into_current_version(); + // Legacy is always the V2 projection of interfaces. + let legacy_v2 = legacy.to_v2(); assert_eq!(legacy_v2.name, format!("Switch1/1/{i}")); assert_eq!(new.name, format!("Switch1/1/{i}")); assert_eq!(legacy_v2.name, new.name); } } - /// Hand-serializes a Device omitting the trailing `new_interfaces` vec, then - /// asserts `Device::try_from` populates `new_interfaces` from the legacy + /// Hand-serializes a Device omitting the trailing `interfaces` vec, then + /// asserts `Device::try_from` populates `interfaces` from the legacy /// `interfaces` vec via the per-variant TryFrom rebuild path. #[test] fn test_device_legacy_account_rebuilds_new_vec() { let device = sample_device_with_n_interfaces(2); - // Hand-serialize all fields except the trailing new_interfaces vec. + // Hand-serialize all fields except the trailing interfaces vec. let mut bytes: Vec = Vec::new(); device.account_type.serialize(&mut bytes).unwrap(); device.owner.serialize(&mut bytes).unwrap(); @@ -1775,7 +1731,14 @@ mod test_device_new_interfaces_vec { device.metrics_publisher_pk.serialize(&mut bytes).unwrap(); device.contributor_pk.serialize(&mut bytes).unwrap(); device.mgmt_vrf.serialize(&mut bytes).unwrap(); - device.interfaces.serialize(&mut bytes).unwrap(); + // Project the legacy slot from `interfaces` (always V2, matching the + // post-#3653 default) so the rebuild path has data to walk. + let legacy: Vec = device + .interfaces + .iter() + .map(|n| InterfaceDeprecated::V2(InterfaceV2::from(n))) + .collect(); + legacy.serialize(&mut bytes).unwrap(); device.reference_count.serialize(&mut bytes).unwrap(); device.users_count.serialize(&mut bytes).unwrap(); device.max_users.serialize(&mut bytes).unwrap(); @@ -1800,18 +1763,18 @@ mod test_device_new_interfaces_vec { .max_multicast_publishers .serialize(&mut bytes) .unwrap(); - // Trailing new_interfaces vec intentionally omitted. + // Trailing interfaces vec intentionally omitted. let decoded = Device::try_from(&bytes[..]).unwrap(); + assert_eq!(decoded.deprecated_interfaces.len(), 2); assert_eq!(decoded.interfaces.len(), 2); - assert_eq!(decoded.new_interfaces.len(), 2); - for (i, new) in decoded.new_interfaces.iter().enumerate() { + for (i, new) in decoded.interfaces.iter().enumerate() { assert_eq!(new.name, format!("Switch1/1/{i}")); assert_eq!(new.version, CURRENT_INTERFACE_SCHEMA_VERSION); } } - /// Forges a `version=5` element at the head of the trailing `new_interfaces` + /// Forges a `version=5` element at the head of the trailing `interfaces` /// slot with junk bytes inside its size envelope. The forward-compat reader /// should advance past the unknown trailing bytes via the size prefix and /// surface both elements. @@ -1820,14 +1783,14 @@ mod test_device_new_interfaces_vec { let device = sample_device_with_n_interfaces(2); // Serialize via the custom serializer, which writes the trailing - // new_interfaces vec at the end. + // interfaces vec at the end. let bytes = borsh::to_vec(&device).unwrap(); - // Re-encode the trailing new_interfaces vec by hand: replace the first + // Re-encode the trailing interfaces vec by hand: replace the first // element with a forged future-version (v5) variant whose body is the // existing v4 body + 7 junk bytes inside the size envelope. - let normal_first_bytes = borsh::to_vec(&device.new_interfaces[0]).unwrap(); - let normal_second_bytes = borsh::to_vec(&device.new_interfaces[1]).unwrap(); + let normal_first_bytes = borsh::to_vec(&device.interfaces[0]).unwrap(); + let normal_second_bytes = borsh::to_vec(&device.interfaces[1]).unwrap(); let v4_body = &normal_first_bytes[3..]; // strip 3-byte size+version prefix let extra: [u8; 7] = [0xAA; 7]; let total_v5 = 3 + v4_body.len() + extra.len(); @@ -1853,22 +1816,22 @@ mod test_device_new_interfaces_vec { forged_bytes.extend_from_slice(&new_trailing); let decoded = Device::try_from(&forged_bytes[..]).unwrap(); - assert_eq!(decoded.new_interfaces.len(), 2); - assert_eq!(decoded.new_interfaces[0].version, 5); - assert_eq!(decoded.new_interfaces[0].name, "Switch1/1/0"); + assert_eq!(decoded.interfaces.len(), 2); + assert_eq!(decoded.interfaces[0].version, 5); + assert_eq!(decoded.interfaces[0].name, "Switch1/1/0"); assert_eq!( - decoded.new_interfaces[1].version, + decoded.interfaces[1].version, CURRENT_INTERFACE_SCHEMA_VERSION ); - assert_eq!(decoded.new_interfaces[1].name, "Switch1/1/1"); + assert_eq!(decoded.interfaces[1].name, "Switch1/1/1"); } /// Mirrors what TopologyBackfill produces: a Vpnv4 loopback whose /// `flex_algo_node_segments` is populated only on the new vec. After a full - /// borsh round-trip, segments must survive in `new_interfaces`, while the + /// borsh round-trip, segments must survive in `interfaces`, while the /// V2-projected legacy vec carries no segments (V2 has no such field). #[test] - fn test_flex_algo_segments_roundtrip_through_new_interfaces() { + fn test_flex_algo_segments_roundtrip_through_interfaces() { use crate::state::topology::FlexAlgoNodeSegment; let mut device = sample_device_with_n_interfaces(1); @@ -1877,27 +1840,24 @@ mod test_device_new_interfaces_vec { topology, node_segment_idx: 42, }; - device.new_interfaces[0].loopback_type = LoopbackType::Vpnv4; - device.new_interfaces[0] + device.interfaces[0].loopback_type = LoopbackType::Vpnv4; + device.interfaces[0] .flex_algo_node_segments .push(segment.clone()); let bytes = borsh::to_vec(&device).unwrap(); let decoded = Device::try_from(&bytes[..]).unwrap(); - // Source of truth: segments survive in new_interfaces. - assert_eq!(decoded.new_interfaces.len(), 1); - assert_eq!( - decoded.new_interfaces[0].flex_algo_node_segments, - vec![segment] - ); - assert_eq!(decoded.new_interfaces[0].loopback_type, LoopbackType::Vpnv4); + // Source of truth: segments survive in interfaces. + assert_eq!(decoded.interfaces.len(), 1); + assert_eq!(decoded.interfaces[0].flex_algo_node_segments, vec![segment]); + assert_eq!(decoded.interfaces[0].loopback_type, LoopbackType::Vpnv4); // V2-projected legacy vec preserves the rest of the interface but cannot // carry segments (V2 has no such field). - assert_eq!(decoded.interfaces.len(), 1); - let legacy_v2 = decoded.interfaces[0].into_current_version(); - assert_eq!(legacy_v2.name, decoded.new_interfaces[0].name); + assert_eq!(decoded.deprecated_interfaces.len(), 1); + let legacy_v2 = decoded.deprecated_interfaces[0].to_v2(); + assert_eq!(legacy_v2.name, decoded.interfaces[0].name); assert_eq!(legacy_v2.loopback_type, LoopbackType::Vpnv4); } } diff --git a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs index d7fba06759..c271a155d3 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs @@ -151,8 +151,8 @@ impl InterfaceV1 { Self::size_given_name_len(self.name.len()) } - pub fn to_interface(&self) -> Interface { - Interface::V1(self.clone()) + pub fn to_interface(&self) -> InterfaceDeprecated { + InterfaceDeprecated::V1(self.clone()) } pub fn size_given_name_len(name_len: usize) -> usize { @@ -315,8 +315,8 @@ impl InterfaceV2 { Self::size_given_name_len(self.name.len()) } - pub fn to_interface(&self) -> Interface { - Interface::V2(self.clone()) + pub fn to_interface(&self) -> InterfaceDeprecated { + InterfaceDeprecated::V2(self.clone()) } pub fn size_given_name_len(name_len: usize) -> usize { @@ -442,8 +442,8 @@ impl InterfaceV3 { Self::size_given_name_len(self.name.len()) } - pub fn to_interface(&self) -> Interface { - Interface::V3(self.clone()) + pub fn to_interface(&self) -> InterfaceDeprecated { + InterfaceDeprecated::V3(self.clone()) } pub fn size_given_name_len(name_len: usize) -> usize { @@ -492,7 +492,7 @@ impl Default for InterfaceV3 { #[derive(BorshSerialize, Debug, PartialEq, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[borsh(use_discriminant = true)] -pub enum Interface { +pub enum InterfaceDeprecated { V1(InterfaceV1) = 0, /// Discriminant 1: V2 format. Does NOT include flex_algo_node_segments. V2(InterfaceV2) = 1, @@ -501,57 +501,50 @@ pub enum Interface { V3(InterfaceV3) = 3, } -impl borsh::BorshDeserialize for Interface { +impl borsh::BorshDeserialize for InterfaceDeprecated { fn deserialize_reader(reader: &mut R) -> borsh::io::Result { let discriminant: u8 = borsh::BorshDeserialize::deserialize_reader(reader)?; match discriminant { - 0 => Ok(Interface::V1(borsh::BorshDeserialize::deserialize_reader( - reader, - )?)), - 1 | 2 => Ok(Interface::V2(borsh::BorshDeserialize::deserialize_reader( - reader, - )?)), - 3 => Ok(Interface::V3(borsh::BorshDeserialize::deserialize_reader( - reader, - )?)), - _ => Ok(Interface::V3(InterfaceV3::default())), + 0 => Ok(InterfaceDeprecated::V1( + borsh::BorshDeserialize::deserialize_reader(reader)?, + )), + 1 | 2 => Ok(InterfaceDeprecated::V2( + borsh::BorshDeserialize::deserialize_reader(reader)?, + )), + 3 => Ok(InterfaceDeprecated::V3( + borsh::BorshDeserialize::deserialize_reader(reader)?, + )), + _ => Ok(InterfaceDeprecated::V3(InterfaceV3::default())), } } } -pub type CurrentInterfaceVersion = InterfaceV2; - -impl Interface { - pub fn into_current_version(&self) -> CurrentInterfaceVersion { +impl InterfaceDeprecated { + /// Convert any legacy variant to its V2 projection. V1 and V3 fan in via + /// `TryFrom<&InterfaceVN>` for `InterfaceV2`; conversion failures fall back + /// to `InterfaceV2::default()`. + pub fn to_v2(&self) -> InterfaceV2 { match self { - Interface::V1(v1) => v1.try_into().unwrap_or_default(), - Interface::V2(v2) => v2.clone(), - Interface::V3(v3) => v3.try_into().unwrap_or_default(), - } - } - - pub fn into_v3(&self) -> InterfaceV3 { - match self { - Interface::V1(v1) => v1.try_into().unwrap_or_default(), - Interface::V2(v2) => v2.clone().into(), - Interface::V3(v3) => v3.clone(), + InterfaceDeprecated::V1(v1) => v1.try_into().unwrap_or_default(), + InterfaceDeprecated::V2(v2) => v2.clone(), + InterfaceDeprecated::V3(v3) => v3.try_into().unwrap_or_default(), } } pub fn size(&self) -> usize { let base_size = match self { - Interface::V1(v1) => v1.size(), - Interface::V2(v2) => v2.size(), - Interface::V3(v3) => v3.size(), + InterfaceDeprecated::V1(v1) => v1.size(), + InterfaceDeprecated::V2(v2) => v2.size(), + InterfaceDeprecated::V3(v3) => v3.size(), }; base_size + 1 // +1 for the enum discriminant } } -impl Validate for Interface { +impl Validate for InterfaceDeprecated { fn validate(&self) -> Result<(), DoubleZeroError> { // Validate each interface - let interface = self.into_current_version(); + let interface = self.to_v2(); if interface.status == InterfaceStatus::Deleting { return Ok(()); @@ -601,7 +594,7 @@ impl Validate for Interface { } } -impl TryFrom<&[u8]> for Interface { +impl TryFrom<&[u8]> for InterfaceDeprecated { type Error = ProgramError; fn try_from(data: &[u8]) -> Result { @@ -617,7 +610,7 @@ pub const CURRENT_INTERFACE_SCHEMA_VERSION: u8 = 4; /// the custom Borsh impls read/write them. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct NewInterface { +pub struct Interface { pub size: u16, pub version: u8, pub status: InterfaceStatus, @@ -637,7 +630,7 @@ pub struct NewInterface { pub flex_algo_node_segments: Vec, } -impl NewInterface { +impl Interface { fn serialize_body(&self, w: &mut W) -> borsh::io::Result<()> { self.status.serialize(w)?; self.name.serialize(w)?; @@ -671,7 +664,7 @@ impl NewInterface { } } -impl borsh::BorshSerialize for NewInterface { +impl borsh::BorshSerialize for Interface { fn serialize(&self, writer: &mut W) -> borsh::io::Result<()> { let mut body: Vec = Vec::new(); self.serialize_body(&mut body)?; @@ -680,7 +673,7 @@ impl borsh::BorshSerialize for NewInterface { if total > u16::MAX as usize { return Err(borsh::io::Error::new( borsh::io::ErrorKind::InvalidData, - "NewInterface exceeds u16 size cap", + "Interface exceeds u16 size cap", )); } @@ -691,7 +684,7 @@ impl borsh::BorshSerialize for NewInterface { } } -impl borsh::BorshDeserialize for NewInterface { +impl borsh::BorshDeserialize for Interface { fn deserialize_reader(reader: &mut R) -> borsh::io::Result { let size: u16 = BorshDeserialize::deserialize_reader(reader)?; let version: u8 = BorshDeserialize::deserialize_reader(reader)?; @@ -702,7 +695,7 @@ impl borsh::BorshDeserialize for NewInterface { reader.read_exact(&mut body)?; let mut s: &[u8] = &body; - Ok(NewInterface { + Ok(Interface { size, version, status: BorshDeserialize::deserialize(&mut s).unwrap_or_default(), @@ -727,7 +720,7 @@ impl borsh::BorshDeserialize for NewInterface { } } -impl Default for NewInterface { +impl Default for Interface { fn default() -> Self { let mut iface = Self { size: 0, @@ -753,7 +746,7 @@ impl Default for NewInterface { } } -impl TryFrom<&InterfaceV1> for NewInterface { +impl TryFrom<&InterfaceV1> for Interface { type Error = ProgramError; fn try_from(v1: &InterfaceV1) -> Result { @@ -762,11 +755,11 @@ impl TryFrom<&InterfaceV1> for NewInterface { } } -impl TryFrom<&InterfaceV2> for NewInterface { +impl TryFrom<&InterfaceV2> for Interface { type Error = ProgramError; fn try_from(v2: &InterfaceV2) -> Result { - let mut iface = NewInterface { + let mut iface = Interface { size: 0, version: CURRENT_INTERFACE_SCHEMA_VERSION, status: v2.status, @@ -790,8 +783,8 @@ impl TryFrom<&InterfaceV2> for NewInterface { } } -impl From<&NewInterface> for InterfaceV2 { - fn from(n: &NewInterface) -> Self { +impl From<&Interface> for InterfaceV2 { + fn from(n: &Interface) -> Self { // V2-on-disk projection drops flex_algo_node_segments per #3653. InterfaceV2 { status: n.status, @@ -812,7 +805,7 @@ impl From<&NewInterface> for InterfaceV2 { } } -impl Validate for NewInterface { +impl Validate for Interface { fn validate(&self) -> Result<(), DoubleZeroError> { if self.status == InterfaceStatus::Deleting { return Ok(()); @@ -868,10 +861,10 @@ fn test_interface_version() { .to_interface(); assert!( - matches!(iface, Interface::V1(_)), - "iface is not Interface::V1" + matches!(iface, InterfaceDeprecated::V1(_)), + "iface is not InterfaceDeprecated::V1" ); - let iface_v2: CurrentInterfaceVersion = iface.into_current_version(); + let iface_v2 = iface.to_v2(); assert_eq!(iface_v2.name, "Loopback0"); assert_eq!(iface_v2.interface_type, InterfaceType::Loopback); assert_eq!(iface_v2.loopback_type, LoopbackType::Ipv4); @@ -900,10 +893,10 @@ fn test_interface_version() { .to_interface(); assert!( - matches!(iface, Interface::V3(_)), - "iface is not Interface::V3" + matches!(iface, InterfaceDeprecated::V3(_)), + "iface is not InterfaceDeprecated::V3" ); - let iface_v3: CurrentInterfaceVersion = iface.into_current_version(); + let iface_v3 = iface.to_v2(); assert_eq!(iface_v3.name, "Loopback0"); assert_eq!(iface_v3.interface_type, InterfaceType::Loopback); assert_eq!(iface_v3.loopback_type, LoopbackType::Ipv4); @@ -940,14 +933,14 @@ mod test_interface_validate { #[test] fn test_valid_interface() { let iface = base_interface(); - assert!(Interface::V2(iface).validate().is_ok()); + assert!(InterfaceDeprecated::V2(iface).validate().is_ok()); } #[test] fn test_invalid_name() { let mut iface = base_interface(); iface.name = "".to_string(); - let err = Interface::V2(iface).validate(); + let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::InvalidInterfaceName); } @@ -955,7 +948,7 @@ mod test_interface_validate { fn test_invalid_vlan_id() { let mut iface = base_interface(); iface.vlan_id = 5000; - let err = Interface::V2(iface).validate(); + let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::InvalidVlanId); } @@ -963,7 +956,7 @@ mod test_interface_validate { fn test_invalid_ip() { let mut iface = base_interface(); iface.ip_net = "8.8.8.8/24".parse().unwrap(); - let err = Interface::V2(iface).validate(); + let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::InvalidInterfaceIp); } @@ -973,7 +966,7 @@ mod test_interface_validate { iface.name = "Loopback100".to_string(); iface.interface_type = InterfaceType::Loopback; iface.interface_cyoa = InterfaceCYOA::GREOverDIA; - let err = Interface::V2(iface).validate(); + let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::CyoaRequiresPhysical); } @@ -984,7 +977,7 @@ mod test_interface_validate { iface.interface_type = InterfaceType::Loopback; iface.interface_cyoa = InterfaceCYOA::GREOverDIA; // this can't happen through validation but should be delete-able iface.status = InterfaceStatus::Deleting; - assert!(Interface::V2(iface).validate().is_ok()); + assert!(InterfaceDeprecated::V2(iface).validate().is_ok()); } #[test] @@ -993,7 +986,7 @@ mod test_interface_validate { iface.interface_type = InterfaceType::Physical; iface.interface_cyoa = InterfaceCYOA::GREOverDIA; iface.ip_net = "38.104.127.117/31".parse().unwrap(); - assert!(Interface::V2(iface).validate().is_ok()); + assert!(InterfaceDeprecated::V2(iface).validate().is_ok()); } #[test] @@ -1004,7 +997,7 @@ mod test_interface_validate { iface.ip_net = "195.219.138.96/32".parse().unwrap(); iface.user_tunnel_endpoint = true; - assert!(Interface::V2(iface).validate().is_ok()); + assert!(InterfaceDeprecated::V2(iface).validate().is_ok()); } #[test] @@ -1015,7 +1008,7 @@ mod test_interface_validate { iface.ip_net = "195.219.138.96/32".parse().unwrap(); iface.user_tunnel_endpoint = false; - let err = Interface::V2(iface).validate(); + let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::InvalidInterfaceIp); } @@ -1042,8 +1035,8 @@ mod test_interface_validate { flex_algo_node_segments: vec![], }; - // Serialize as Interface::V3 (with enum discriminant) - let interface_enum = Interface::V3(iface.clone()); + // Serialize as InterfaceDeprecated::V3 (with enum discriminant) + let interface_enum = InterfaceDeprecated::V3(iface.clone()); let bytes = borsh::to_vec(&interface_enum).unwrap(); println!("\n=== InterfaceV3 Serialization Debug ==="); @@ -1177,8 +1170,8 @@ mod test_new_interface { use crate::state::topology::FlexAlgoNodeSegment; use solana_program::pubkey::Pubkey; - fn sample_new_interface() -> NewInterface { - let mut iface = NewInterface { + fn sample_new_interface() -> Interface { + let mut iface = Interface { size: 0, version: CURRENT_INTERFACE_SCHEMA_VERSION, status: InterfaceStatus::Activated, @@ -1210,13 +1203,13 @@ mod test_new_interface { let bytes = borsh::to_vec(&iface).unwrap(); assert_eq!(bytes.len(), iface.size as usize); - let decoded: NewInterface = borsh::from_slice(&bytes).unwrap(); + let decoded: Interface = borsh::from_slice(&bytes).unwrap(); assert_eq!(decoded, iface); } #[test] fn test_new_interface_default_size_stamped() { - let iface = NewInterface::default(); + let iface = Interface::default(); assert_eq!(iface.version, CURRENT_INTERFACE_SCHEMA_VERSION); let bytes = borsh::to_vec(&iface).unwrap(); assert_eq!(bytes.len(), iface.size as usize); @@ -1247,8 +1240,8 @@ mod test_new_interface { concat.extend_from_slice(&normal_bytes); let mut reader: &[u8] = &concat; - let first = ::deserialize_reader(&mut reader).unwrap(); - let second = ::deserialize_reader(&mut reader).unwrap(); + let first = ::deserialize_reader(&mut reader).unwrap(); + let second = ::deserialize_reader(&mut reader).unwrap(); assert!(reader.is_empty(), "reader should be fully consumed"); // First element: known fields decode identically to `normal`; size/version reflect @@ -1289,7 +1282,7 @@ mod test_new_interface { node_segment_idx: 200, user_tunnel_endpoint: true, }; - let n: NewInterface = (&v1).try_into().unwrap(); + let n: Interface = (&v1).try_into().unwrap(); assert_eq!(n.version, CURRENT_INTERFACE_SCHEMA_VERSION); assert_eq!(n.status, v1.status); @@ -1331,7 +1324,7 @@ mod test_new_interface { node_segment_idx: 7, user_tunnel_endpoint: false, }; - let n: NewInterface = (&v2).try_into().unwrap(); + let n: Interface = (&v2).try_into().unwrap(); assert_eq!(n.version, CURRENT_INTERFACE_SCHEMA_VERSION); assert_eq!(n.status, v2.status); @@ -1358,15 +1351,15 @@ mod test_new_interface { let n = sample_new_interface(); assert!(!n.flex_algo_node_segments.is_empty()); let v2: InterfaceV2 = (&n).into(); - // V2 has no segments field; round-trip back to NewInterface yields empty segments. - let back: NewInterface = (&v2).try_into().unwrap(); + // V2 has no segments field; round-trip back to Interface yields empty segments. + let back: Interface = (&v2).try_into().unwrap(); assert!(back.flex_algo_node_segments.is_empty()); assert_eq!(back.name, n.name); assert_eq!(back.bandwidth, n.bandwidth); } - fn base_validate_interface() -> NewInterface { - let mut iface = NewInterface { + fn base_validate_interface() -> Interface { + let mut iface = Interface { size: 0, version: CURRENT_INTERFACE_SCHEMA_VERSION, status: InterfaceStatus::Activated, diff --git a/smartcontract/programs/doublezero-serviceability/tests/delete_cyoa_interface_test.rs b/smartcontract/programs/doublezero-serviceability/tests/delete_cyoa_interface_test.rs index 93ef6edd3f..c3819c45c7 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/delete_cyoa_interface_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/delete_cyoa_interface_test.rs @@ -140,8 +140,7 @@ async fn test_delete_cyoa_interface_with_invalid_sibling() { metrics_publisher_pk: Pubkey::default(), contributor_pk: contributor_pubkey, mgmt_vrf: "mgmt".to_string(), - interfaces: vec![iface_a.to_interface(), iface_b.to_interface()], - new_interfaces: vec![ + interfaces: vec![ (&iface_a).try_into().unwrap(), (&iface_b).try_into().unwrap(), ], @@ -157,6 +156,7 @@ async fn test_delete_cyoa_interface_with_invalid_sibling() { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let dev_data = borsh::to_vec(&device).unwrap(); program_test.add_account( @@ -376,8 +376,7 @@ async fn test_update_cyoa_interface_with_invalid_sibling() { metrics_publisher_pk: Pubkey::default(), contributor_pk: contributor_pubkey, mgmt_vrf: "mgmt".to_string(), - interfaces: vec![iface_a.to_interface(), iface_b.to_interface()], - new_interfaces: vec![ + interfaces: vec![ (&iface_a).try_into().unwrap(), (&iface_b).try_into().unwrap(), ], @@ -393,6 +392,7 @@ async fn test_update_cyoa_interface_with_invalid_sibling() { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let dev_data = borsh::to_vec(&device).unwrap(); program_test.add_account( diff --git a/smartcontract/programs/doublezero-serviceability/tests/interface_onchain_allocation_test.rs b/smartcontract/programs/doublezero-serviceability/tests/interface_onchain_allocation_test.rs index 38b444cf09..1057a11c54 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/interface_onchain_allocation_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/interface_onchain_allocation_test.rs @@ -382,7 +382,7 @@ async fn test_create_loopback_vpnv4_with_onchain_allocation() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.status, InterfaceStatus::Activated); assert_eq!(iface.interface_type, InterfaceType::Loopback); @@ -460,7 +460,7 @@ async fn test_create_loopback_non_vpnv4_with_onchain_allocation() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.status, InterfaceStatus::Activated); assert_ne!( @@ -542,7 +542,7 @@ async fn test_create_loopback_with_onchain_allocation_honors_supplied_ip_net() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.status, InterfaceStatus::Activated); assert_eq!(iface.interface_type, InterfaceType::Loopback); @@ -626,7 +626,7 @@ async fn test_create_physical_with_onchain_allocation() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.status, InterfaceStatus::Unlinked); assert_eq!(iface.interface_type, InterfaceType::Physical); @@ -684,7 +684,7 @@ async fn test_create_interface_backward_compat() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.status, InterfaceStatus::Pending); assert_eq!(iface.ip_net, NetworkV4::default()); @@ -816,10 +816,7 @@ async fn test_delete_loopback_with_onchain_deallocation() { .await .expect("Device not found"); assert_eq!(device.interfaces.len(), 1); - assert_eq!( - device.interfaces[0].into_current_version().status, - InterfaceStatus::Activated - ); + assert_eq!(device.interfaces[0].status, InterfaceStatus::Activated); // Atomic delete+deallocate execute_transaction( @@ -916,10 +913,7 @@ async fn test_delete_physical_with_onchain_deallocation() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - assert_eq!( - device.interfaces[0].into_current_version().status, - InterfaceStatus::Unlinked - ); + assert_eq!(device.interfaces[0].status, InterfaceStatus::Unlinked); // Atomic delete execute_transaction( @@ -1035,10 +1029,7 @@ async fn test_delete_interface_backward_compat() { .await .expect("Device not found"); assert_eq!(device.interfaces.len(), 1); - assert_eq!( - device.interfaces[0].into_current_version().status, - InterfaceStatus::Deleting - ); + assert_eq!(device.interfaces[0].status, InterfaceStatus::Deleting); println!("test_delete_interface_backward_compat PASSED"); } @@ -1101,7 +1092,7 @@ async fn test_update_interface_node_segment_idx_onchain_alloc() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.node_segment_idx, 42); // Verify ID 42 is allocated in the resource extension @@ -1190,7 +1181,7 @@ async fn test_update_interface_node_segment_idx_change_value() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.node_segment_idx, 200); // Verify old ID 100 is deallocated and new ID 200 is allocated @@ -1283,7 +1274,7 @@ async fn test_update_interface_node_segment_idx_clear() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.node_segment_idx, 0); // Verify ID 50 is deallocated @@ -1340,7 +1331,7 @@ async fn test_update_interface_node_segment_idx_legacy() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_current_version(); + let iface = device.interfaces[0].clone(); assert_eq!(iface.node_segment_idx, 42); } diff --git a/smartcontract/programs/doublezero-serviceability/tests/link_wan_test.rs b/smartcontract/programs/doublezero-serviceability/tests/link_wan_test.rs index 7c621e759e..20d970adde 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/link_wan_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/link_wan_test.rs @@ -258,7 +258,7 @@ async fn test_wan_link() { assert_eq!(device_a.code, "a".to_string()); assert_eq!(device_a.status, DeviceStatus::Pending); - let iface = device_a.interfaces.first().unwrap().into_current_version(); + let iface = device_a.interfaces.first().unwrap(); assert_eq!(iface.name, "Ethernet0".to_string()); assert_eq!(iface.interface_type, InterfaceType::Physical); assert_eq!(iface.loopback_type, LoopbackType::None); @@ -291,7 +291,7 @@ async fn test_wan_link() { .get_device() .unwrap(); - let iface = device_a.interfaces.first().unwrap().into_current_version(); + let iface = device_a.interfaces.first().unwrap(); assert_eq!(iface.name, "Ethernet0".to_string()); assert_eq!(iface.ip_net, "10.0.0.0/31".parse().unwrap()); assert_eq!(iface.status, InterfaceStatus::Activated); @@ -389,7 +389,7 @@ async fn test_wan_link() { assert_eq!(device_z.code, "z".to_string()); assert_eq!(device_z.status, DeviceStatus::Pending); - let iface = device_z.interfaces.first().unwrap().into_current_version(); + let iface = device_z.interfaces.first().unwrap(); assert_eq!(iface.name, "Ethernet1".to_string()); assert_eq!(iface.interface_type, InterfaceType::Physical); assert_eq!(iface.loopback_type, LoopbackType::None); @@ -422,7 +422,7 @@ async fn test_wan_link() { .get_device() .unwrap(); - let iface = device_z.interfaces.first().unwrap().into_current_version(); + let iface = device_z.interfaces.first().unwrap(); assert_eq!(iface.name, "Ethernet1".to_string()); assert_eq!(iface.ip_net, "10.0.0.1/31".parse().unwrap()); assert_eq!(iface.status, InterfaceStatus::Activated); diff --git a/smartcontract/programs/doublezero-serviceability/tests/topology_test.rs b/smartcontract/programs/doublezero-serviceability/tests/topology_test.rs index 44f230e4d9..e757c4de62 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/topology_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/topology_test.rs @@ -1690,7 +1690,7 @@ async fn test_topology_backfill_populates_vpnv4_loopbacks() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_v3(); + let iface = device.interfaces[0].clone(); assert_eq!( iface.flex_algo_node_segments.len(), 0, @@ -1719,11 +1719,11 @@ async fn test_topology_backfill_populates_vpnv4_loopbacks() { banks_client.process_transaction(tx).await.unwrap(); // Verify: loopback now has 1 segment pointing to the topology. - // Post-#3665, segments live in `new_interfaces`. + // Post-#3665, segments live in `interfaces`. let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found after backfill"); - let new_iface = &device.new_interfaces[0]; + let new_iface = &device.interfaces[0]; assert_eq!( new_iface.flex_algo_node_segments.len(), 1, @@ -1750,7 +1750,7 @@ async fn test_topology_backfill_populates_vpnv4_loopbacks() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found after second backfill"); - let new_iface = &device.new_interfaces[0]; + let new_iface = &device.interfaces[0]; assert_eq!( new_iface.flex_algo_node_segments.len(), 1, @@ -2053,7 +2053,7 @@ async fn test_topology_backfill_allocates_sr_id_from_onchain_resource() { let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found"); - let iface = device.interfaces[0].into_v3(); + let iface = device.interfaces[0].clone(); assert_eq!( iface.node_segment_idx, 1, "Base node_segment_idx should be 1 (first ID from SegmentRoutingIds)" @@ -2098,12 +2098,12 @@ async fn test_topology_backfill_allocates_sr_id_from_onchain_resource() { banks_client.process_transaction(tx).await.unwrap(); // Verify: backfill stored a flex-algo segment with the next SR ID (2). - // Post-#3665, segments live in `new_interfaces` (the legacy `interfaces` slot + // Post-#3665, segments live in `interfaces` (the legacy `interfaces` slot // is always V2-projected on save and so does not carry segments). let device = get_device(&mut banks_client, device_pubkey) .await .expect("Device not found after backfill"); - let new_iface = &device.new_interfaces[0]; + let new_iface = &device.interfaces[0]; assert_eq!( new_iface.node_segment_idx, 1, "Base node_segment_idx must remain 1" diff --git a/smartcontract/programs/doublezero-telemetry/tests/initialize_device_latency_samples_tests.rs b/smartcontract/programs/doublezero-telemetry/tests/initialize_device_latency_samples_tests.rs index e763893e69..c087089bd7 100644 --- a/smartcontract/programs/doublezero-telemetry/tests/initialize_device_latency_samples_tests.rs +++ b/smartcontract/programs/doublezero-telemetry/tests/initialize_device_latency_samples_tests.rs @@ -517,7 +517,6 @@ async fn test_initialize_device_latency_samples_fail_origin_device_wrong_owner() dz_prefixes: NetworkV4List::default(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 0, device_health: DeviceHealth::Pending, @@ -529,6 +528,7 @@ async fn test_initialize_device_latency_samples_fail_origin_device_wrong_owner() reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mut device_data = Vec::new(); @@ -613,7 +613,6 @@ async fn test_initialize_device_latency_samples_fail_target_device_wrong_owner() code: "invalid".to_string(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], users_count: 0, max_users: 0, device_health: DeviceHealth::Pending, @@ -625,6 +624,7 @@ async fn test_initialize_device_latency_samples_fail_target_device_wrong_owner() reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; let mut data = Vec::new(); diff --git a/smartcontract/sdk/go/serviceability/client_test.go b/smartcontract/sdk/go/serviceability/client_test.go index 7f7437fc2c..ba9a4ad54e 100644 --- a/smartcontract/sdk/go/serviceability/client_test.go +++ b/smartcontract/sdk/go/serviceability/client_test.go @@ -265,7 +265,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { MetricsPublisherPubKey: getPubKeyOffset(devicePayload, 141, 173), ContributorPubKey: getPubKeyOffset(devicePayload, 173, 205), MgmtVrf: "default", - Interfaces: []Interface{ + DeprecatedInterfaces: []Interface{ { Version: 0, Status: InterfaceStatusPending, @@ -292,10 +292,10 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ReferenceCount: 1234, UsersCount: 110, MaxUsers: 128, - // Legacy fixture has no trailing new_interfaces vec; the rebuild - // path projects each legacy Interface into a NewInterface stamped + // Legacy fixture has no trailing interfaces vec; the rebuild + // path projects each legacy enum entry into an Interface stamped // with the current schema version. - NewInterfaces: []Interface{ + Interfaces: []Interface{ { Version: CurrentInterfaceVersion, Status: InterfaceStatusPending, diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index 80da5cef0c..f07fc4f3de 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -92,10 +92,10 @@ func DeserializeContributor(reader *ByteReader, contributor *Contributor) { // 2 — reserved, never written // 3 — V3: V2 fields + flex_algo_node_segments (RFC-18) // -// The on-chain Device serializer projects the legacy interfaces slot as V2 +// The on-chain Device serializer projects the legacy deprecated_interfaces slot as V2 // (per #3653); discriminant-3 entries seen here are residual from older // accounts. Newer flex_algo_node_segments data lives in the trailing -// new_interfaces vec on Device, not in this slot. +// interfaces vec on Device, not in this slot. func DeserializeInterface(reader *ByteReader, iface *Interface) { iface.Version = reader.ReadU8() @@ -150,7 +150,7 @@ func DeserializeInterfaceV3(reader *ByteReader, iface *Interface) { } } -// DeserializeInterfaceSized reads a single size-prefixed NewInterface element. +// DeserializeInterfaceSized reads a single size-prefixed Interface element. // // Wire format: u16 size + u8 version + body, where size includes the 3-byte prefix. // Forward-compat readers always advance the cursor to start+size after reading the @@ -212,7 +212,7 @@ func DeserializeDevice(reader *ByteReader, dev *Device) { dev.MetricsPublisherPubKey = reader.ReadPubkey() dev.ContributorPubKey = reader.ReadPubkey() dev.MgmtVrf = reader.ReadString() - dev.Interfaces = make([]Interface, 0) + dev.DeprecatedInterfaces = make([]Interface, 0) length := reader.ReadU32() if length > 0 && (length*18) > reader.Remaining() { log.Println("DeserializeDevice: Not enough data for interfaces (# of interfaces = ", length, ")") @@ -221,7 +221,7 @@ func DeserializeDevice(reader *ByteReader, dev *Device) { for i := uint32(0); i < length; i++ { var iface Interface DeserializeInterface(reader, &iface) - dev.Interfaces = append(dev.Interfaces, iface) + dev.DeprecatedInterfaces = append(dev.DeprecatedInterfaces, iface) } dev.ReferenceCount = reader.ReadU32() dev.UsersCount = reader.ReadU16() @@ -236,28 +236,28 @@ func DeserializeDevice(reader *ByteReader, dev *Device) { dev.MulticastPublishersCount = reader.ReadU16() dev.MaxMulticastPublishers = reader.ReadU16() - // Trailing new_interfaces vec (size-prefixed). Empty trailing => rebuild from legacy. + // Trailing interfaces vec (size-prefixed). Empty trailing => rebuild from deprecated_interfaces. // Length mismatch => surface via dev.DeserializeError without aborting earlier fields. if reader.Remaining() == 0 { - dev.NewInterfaces = make([]Interface, len(dev.Interfaces)) - for i, legacy := range dev.Interfaces { + dev.Interfaces = make([]Interface, len(dev.DeprecatedInterfaces)) + for i, legacy := range dev.DeprecatedInterfaces { ni := legacy ni.Version = CurrentInterfaceVersion ni.Size = 0 - dev.NewInterfaces[i] = ni + dev.Interfaces[i] = ni } } else { newLen := reader.ReadU32() - if int(newLen) != len(dev.Interfaces) { + if int(newLen) != len(dev.DeprecatedInterfaces) { dev.DeserializeError = fmt.Errorf( - "DeserializeDevice: new_interfaces length %d != interfaces length %d", - newLen, len(dev.Interfaces), + "DeserializeDevice: interfaces length %d != deprecated_interfaces length %d", + newLen, len(dev.DeprecatedInterfaces), ) return } - dev.NewInterfaces = make([]Interface, newLen) + dev.Interfaces = make([]Interface, newLen) for i := uint32(0); i < newLen; i++ { - DeserializeInterfaceSized(reader, &dev.NewInterfaces[i]) + DeserializeInterfaceSized(reader, &dev.Interfaces[i]) } } // Note: dev.PubKey is set separately in client.go after deserialization diff --git a/smartcontract/sdk/go/serviceability/deserialize_test.go b/smartcontract/sdk/go/serviceability/deserialize_test.go index a8714360e3..866d7b6daf 100644 --- a/smartcontract/sdk/go/serviceability/deserialize_test.go +++ b/smartcontract/sdk/go/serviceability/deserialize_test.go @@ -14,11 +14,11 @@ import ( // // Wire format mirrors smartcontract/programs/doublezero-serviceability::state::device. // Field order through max_multicast_publishers, then optionally a trailing -// new_interfaces vec where each element is laid out as: +// interfaces vec where each element is laid out as: // // u16 size (incl. 3-byte prefix) | u8 version | body // -// Body layout matches Rust NewInterface::serialize_body — see that function for +// Body layout matches Rust Interface::serialize_body — see that function for // the canonical ordering. type deviceBuilder struct { buf bytes.Buffer @@ -111,7 +111,7 @@ func writeLegacyInterfaceV2(b *deviceBuilder, name string) { } // newInterfaceBody returns the body bytes (no size/version prefix) for a -// minimal V4 NewInterface element with caller-provided name. +// minimal V4 Interface element with caller-provided name. func newInterfaceBody(name string) []byte { var body bytes.Buffer body.WriteByte(0) // status @@ -133,7 +133,7 @@ func newInterfaceBody(name string) []byte { return body.Bytes() } -// writeNewInterfaceSized appends a single size-prefixed NewInterface (version 4) +// writeNewInterfaceSized appends a single size-prefixed Interface (version 4) // to the builder. size = 3 + len(body); the size header is included in size. func writeNewInterfaceSized(b *deviceBuilder, name string) { body := newInterfaceBody(name) @@ -144,7 +144,7 @@ func writeNewInterfaceSized(b *deviceBuilder, name string) { } func TestDeserializeInterfaceSized_PopulatedTrailingVec(t *testing.T) { - // Cross-language framing assertion: a NewInterface with empty name has body + // Cross-language framing assertion: an Interface with empty name has body // length = 1+4+1+1+1+1+8+8+2+1+2+5+2+1+4 = 42, so size = 3+42 = 45. const expectedSizeEmptyName = 45 @@ -155,7 +155,7 @@ func TestDeserializeInterfaceSized_PopulatedTrailingVec(t *testing.T) { writeLegacyInterfaceV2(bb, "Lo0") }, func(bb *deviceBuilder) { - bb.writeU32(2) // new_interfaces vec length + bb.writeU32(2) // interfaces vec length writeNewInterfaceSized(bb, "Eth1") writeNewInterfaceSized(bb, "Lo0") }, @@ -166,24 +166,24 @@ func TestDeserializeInterfaceSized_PopulatedTrailingVec(t *testing.T) { serviceability.DeserializeDevice(r, &dev) require.NoError(t, dev.DeserializeError) + require.Len(t, dev.DeprecatedInterfaces, 2) require.Len(t, dev.Interfaces, 2) - require.Len(t, dev.NewInterfaces, 2) - assert.Equal(t, "Eth1", dev.NewInterfaces[0].Name) - assert.Equal(t, "Lo0", dev.NewInterfaces[1].Name) - assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), dev.NewInterfaces[0].Version) + assert.Equal(t, "Eth1", dev.Interfaces[0].Name) + assert.Equal(t, "Lo0", dev.Interfaces[1].Name) + assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), dev.Interfaces[0].Version) - // The size field on NewInterface includes the 2-byte size + 1-byte version + // The size field on Interface includes the 2-byte size + 1-byte version // + body. For an empty-flex-segs body with name "Eth1": 4+4+...; verified // against the expected-empty-name baseline below. emptyBody := newInterfaceBody("") assert.Equal(t, expectedSizeEmptyName, 3+len(emptyBody)) - for i := range dev.NewInterfaces { - expected := uint16(3 + len(newInterfaceBody(dev.NewInterfaces[i].Name))) - assert.Equal(t, expected, dev.NewInterfaces[i].Size, "size mismatch on element %d", i) + for i := range dev.Interfaces { + expected := uint16(3 + len(newInterfaceBody(dev.Interfaces[i].Name))) + assert.Equal(t, expected, dev.Interfaces[i].Size, "size mismatch on element %d", i) } } -func TestDeserializeDevice_LegacyAccountRebuildsNewInterfaces(t *testing.T) { +func TestDeserializeDevice_LegacyAccountRebuildsInterfaces(t *testing.T) { var b deviceBuilder b.writeDevice(2, func(bb *deviceBuilder) { @@ -198,13 +198,13 @@ func TestDeserializeDevice_LegacyAccountRebuildsNewInterfaces(t *testing.T) { serviceability.DeserializeDevice(r, &dev) require.NoError(t, dev.DeserializeError) + require.Len(t, dev.DeprecatedInterfaces, 2) require.Len(t, dev.Interfaces, 2) - require.Len(t, dev.NewInterfaces, 2) - assert.Equal(t, "Eth1", dev.NewInterfaces[0].Name) - assert.Equal(t, "Lo0", dev.NewInterfaces[1].Name) + assert.Equal(t, "Eth1", dev.Interfaces[0].Name) + assert.Equal(t, "Lo0", dev.Interfaces[1].Name) // Rebuilt entries are stamped with the current schema version and zero // size (callers don't need on-disk size for a rebuild). - for _, ni := range dev.NewInterfaces { + for _, ni := range dev.Interfaces { assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), ni.Version) assert.Equal(t, uint16(0), ni.Size) } @@ -227,7 +227,7 @@ func TestDeserializeDevice_TrailingLengthMismatchSetsError(t *testing.T) { var dev serviceability.Device serviceability.DeserializeDevice(r, &dev) require.Error(t, dev.DeserializeError) - assert.Contains(t, dev.DeserializeError.Error(), "length 1 != interfaces length 2") + assert.Contains(t, dev.DeserializeError.Error(), "interfaces length 1 != deprecated_interfaces length 2") } func TestDeserializeInterfaceSized_FutureVersionSkipsTrailingBytes(t *testing.T) { @@ -256,9 +256,9 @@ func TestDeserializeInterfaceSized_FutureVersionSkipsTrailingBytes(t *testing.T) var dev serviceability.Device serviceability.DeserializeDevice(r, &dev) require.NoError(t, dev.DeserializeError) - require.Len(t, dev.NewInterfaces, 1) - assert.Equal(t, uint8(5), dev.NewInterfaces[0].Version) - assert.Equal(t, size, dev.NewInterfaces[0].Size) + require.Len(t, dev.Interfaces, 1) + assert.Equal(t, uint8(5), dev.Interfaces[0].Version) + assert.Equal(t, size, dev.Interfaces[0].Size) // Body fields up to known shape are still parsed. - assert.Equal(t, "Future1", dev.NewInterfaces[0].Name) + assert.Equal(t, "Future1", dev.Interfaces[0].Name) } diff --git a/smartcontract/sdk/go/serviceability/fixture_test.go b/smartcontract/sdk/go/serviceability/fixture_test.go index 10169b857d..8aa7d73837 100644 --- a/smartcontract/sdk/go/serviceability/fixture_test.go +++ b/smartcontract/sdk/go/serviceability/fixture_test.go @@ -48,12 +48,12 @@ func loadFixture(t *testing.T, name string) ([]byte, fixtureMeta) { return bin, meta } -// expectedNewInterfaceSize recomputes the on-disk byte length of a NewInterface +// expectedInterfaceSize recomputes the on-disk byte length of an Interface // element so tests don't bake magic numbers. Layout matches Rust -// NewInterface::serialize_body (interface.rs:641-658): u16 size + u8 version +// Interface::serialize_body (interface.rs:641-658): u16 size + u8 version // (3-byte prefix) + u8 status + (u32+len) name + 4*u8 + u64*2 + u16 + u8 + u16 // + 5-byte ip_net + u16 + u8 + (u32+34*N) flex_algo_node_segments. -func expectedNewInterfaceSize(ni serviceability.Interface) uint16 { +func expectedInterfaceSize(ni serviceability.Interface) uint16 { body := 1 + (4 + len(ni.Name)) + 1 + 1 + 1 + 1 + 8 + 8 + 2 + 1 + 2 + 5 + 2 + 1 + (4 + 34*len(ni.FlexAlgoNodeSegments)) return uint16(3 + body) } @@ -75,31 +75,31 @@ func TestFixtureDevice(t *testing.T) { // Legacy slot is the V2 projection of new_interfaces (always V2 per #3653); // both entries carry version 1 and no FlexAlgoNodeSegments. - require.Len(t, dev.Interfaces, 2) - assert.Equal(t, uint8(1), dev.Interfaces[0].Version) - assert.Equal(t, "Loopback0", dev.Interfaces[0].Name) - assert.Equal(t, serviceability.LoopbackTypeVpnv4, dev.Interfaces[0].LoopbackType) - assert.Empty(t, dev.Interfaces[0].FlexAlgoNodeSegments) - assert.Equal(t, uint8(1), dev.Interfaces[1].Version) - assert.Equal(t, "Ethernet1", dev.Interfaces[1].Name) - assert.True(t, dev.Interfaces[1].UserTunnelEndpoint) + require.Len(t, dev.DeprecatedInterfaces, 2) + assert.Equal(t, uint8(1), dev.DeprecatedInterfaces[0].Version) + assert.Equal(t, "Loopback0", dev.DeprecatedInterfaces[0].Name) + assert.Equal(t, serviceability.LoopbackTypeVpnv4, dev.DeprecatedInterfaces[0].LoopbackType) + assert.Empty(t, dev.DeprecatedInterfaces[0].FlexAlgoNodeSegments) + assert.Equal(t, uint8(1), dev.DeprecatedInterfaces[1].Version) + assert.Equal(t, "Ethernet1", dev.DeprecatedInterfaces[1].Name) + assert.True(t, dev.DeprecatedInterfaces[1].UserTunnelEndpoint) // Trailing new_interfaces vec carries the full V4 NewInterface bodies. - require.Len(t, dev.NewInterfaces, 2) - ni0 := dev.NewInterfaces[0] + require.Len(t, dev.Interfaces, 2) + ni0 := dev.Interfaces[0] assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), ni0.Version) assert.Equal(t, "Loopback0", ni0.Name) assert.Equal(t, serviceability.LoopbackTypeVpnv4, ni0.LoopbackType) require.Len(t, ni0.FlexAlgoNodeSegments, 1) assert.Equal(t, uint16(300), ni0.FlexAlgoNodeSegments[0].NodeSegmentIdx) - assert.Equal(t, expectedNewInterfaceSize(ni0), ni0.Size) + assert.Equal(t, expectedInterfaceSize(ni0), ni0.Size) - ni1 := dev.NewInterfaces[1] + ni1 := dev.Interfaces[1] assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), ni1.Version) assert.Equal(t, "Ethernet1", ni1.Name) assert.True(t, ni1.UserTunnelEndpoint) assert.Empty(t, ni1.FlexAlgoNodeSegments) - assert.Equal(t, expectedNewInterfaceSize(ni1), ni1.Size) + assert.Equal(t, expectedInterfaceSize(ni1), ni1.Size) } // Pre-#3667 on-disk format: legacy `interfaces` vec only, no trailing @@ -115,24 +115,24 @@ func TestFixtureDeviceLegacy(t *testing.T) { require.NoError(t, dev.DeserializeError) // Legacy slot mirrors the original V1+V2 hand-serialized shape. - require.Len(t, dev.Interfaces, 2) - assert.Equal(t, uint8(0), dev.Interfaces[0].Version) // V1 - assert.Equal(t, "Loopback0", dev.Interfaces[0].Name) - assert.Equal(t, uint8(1), dev.Interfaces[1].Version) // V2 - assert.Equal(t, "Ethernet1", dev.Interfaces[1].Name) + require.Len(t, dev.DeprecatedInterfaces, 2) + assert.Equal(t, uint8(0), dev.DeprecatedInterfaces[0].Version) // V1 + assert.Equal(t, "Loopback0", dev.DeprecatedInterfaces[0].Name) + assert.Equal(t, uint8(1), dev.DeprecatedInterfaces[1].Version) // V2 + assert.Equal(t, "Ethernet1", dev.DeprecatedInterfaces[1].Name) // Rebuilt new_interfaces: same field values as the legacy entries, but // stamped with the current schema version and zero on-disk size. - require.Len(t, dev.NewInterfaces, 2) - for _, ni := range dev.NewInterfaces { + require.Len(t, dev.Interfaces, 2) + for _, ni := range dev.Interfaces { assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), ni.Version) assert.Equal(t, uint16(0), ni.Size) assert.Empty(t, ni.FlexAlgoNodeSegments) } - assert.Equal(t, "Loopback0", dev.NewInterfaces[0].Name) - assert.Equal(t, serviceability.LoopbackTypeVpnv4, dev.NewInterfaces[0].LoopbackType) - assert.Equal(t, "Ethernet1", dev.NewInterfaces[1].Name) - assert.True(t, dev.NewInterfaces[1].UserTunnelEndpoint) + assert.Equal(t, "Loopback0", dev.Interfaces[0].Name) + assert.Equal(t, serviceability.LoopbackTypeVpnv4, dev.Interfaces[0].LoopbackType) + assert.Equal(t, "Ethernet1", dev.Interfaces[1].Name) + assert.True(t, dev.Interfaces[1].UserTunnelEndpoint) } // Same on-disk shape as device.bin, but the last trailing-vec element is @@ -147,17 +147,17 @@ func TestFixtureDeviceFutureVersion(t *testing.T) { serviceability.DeserializeDevice(r, &dev) require.NoError(t, dev.DeserializeError) - require.Len(t, dev.NewInterfaces, 2) - ni0 := dev.NewInterfaces[0] + require.Len(t, dev.Interfaces, 2) + ni0 := dev.Interfaces[0] assert.Equal(t, uint8(serviceability.CurrentInterfaceVersion), ni0.Version) assert.Equal(t, "Loopback0", ni0.Name) require.Len(t, ni0.FlexAlgoNodeSegments, 1) // Doctored element: future version stamp + 8 trailing junk bytes the reader // skips via seek(start+size). - ni1 := dev.NewInterfaces[1] + ni1 := dev.Interfaces[1] assert.Equal(t, uint8(5), ni1.Version) - assert.Equal(t, expectedNewInterfaceSize(ni1)+8, ni1.Size) + assert.Equal(t, expectedInterfaceSize(ni1)+8, ni1.Size) assert.Equal(t, "Ethernet1", ni1.Name) assert.True(t, ni1.UserTunnelEndpoint) } diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index c73d37a7d3..d8de6b745c 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -416,8 +416,8 @@ type Interface struct { UserTunnelEndpoint bool // FlexAlgoNodeSegments holds flex-algo node segment assignments for this interface (RFC-18). // Only populated when reading a discriminant-3 (V3) interface; the on-chain Device serializer - // now projects the legacy interfaces slot as V2 (per #3653) and surfaces segments through the - // trailing new_interfaces vec on Device, which this Go SDK does not yet read. Nil otherwise. + // now projects the deprecated_interfaces slot as V2 (per #3653) and surfaces segments through + // the trailing interfaces vec on Device. Nil otherwise. FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` } @@ -444,7 +444,7 @@ func (i Interface) MarshalJSON() ([]byte, error) { } // CurrentInterfaceVersion is the on-wire schema version of the size-prefixed -// NewInterface format (matching Rust's CURRENT_INTERFACE_SCHEMA_VERSION). +// Interface format (matching Rust's CURRENT_INTERFACE_SCHEMA_VERSION). const CurrentInterfaceVersion = 4 type Device struct { @@ -462,7 +462,7 @@ type Device struct { MetricsPublisherPubKey [32]uint8 `influx:"tag,metrics_publisher_pubkey,pubkey"` ContributorPubKey [32]byte `influx:"tag,contributor_pubkey,pubkey"` MgmtVrf string `influx:"field,mgmt_vrf"` - Interfaces []Interface `influx:"-"` + DeprecatedInterfaces []Interface `influx:"-" json:",omitempty"` ReferenceCount uint32 `influx:"field,reference_count"` UsersCount uint16 `influx:"field,users_count"` MaxUsers uint16 `influx:"field,max_users"` @@ -475,13 +475,13 @@ type Device struct { ReservedSeats uint16 `influx:"field,reserved_seats"` MulticastPublishersCount uint16 `influx:"field,multicast_publishers_count"` MaxMulticastPublishers uint16 `influx:"field,max_multicast_publishers"` - // NewInterfaces is the trailing size-prefixed vec parallel to Interfaces. For legacy - // accounts (no trailing bytes), this is rebuilt from Interfaces by DeserializeDevice. - // When populated from the wire, len(NewInterfaces) == len(Interfaces) is enforced. - NewInterfaces []Interface `influx:"-" json:",omitempty"` + // Interfaces is the trailing size-prefixed vec parallel to DeprecatedInterfaces. For legacy + // accounts (no trailing bytes), this is rebuilt from DeprecatedInterfaces by DeserializeDevice. + // When populated from the wire, len(Interfaces) == len(DeprecatedInterfaces) is enforced. + Interfaces []Interface `influx:"-" json:",omitempty"` // DeserializeError is set when DeserializeDevice encounters a recoverable but - // account-malformed condition (e.g. trailing new_interfaces length mismatch with - // the legacy interfaces vec). Consumers should check this before trusting the + // account-malformed condition (e.g. trailing interfaces length mismatch with + // the deprecated_interfaces vec). Consumers should check this before trusting the // deserialized fields. DeserializeError error `influx:"-" json:"-"` PubKey [32]byte `influx:"tag,pubkey,pubkey"` diff --git a/smartcontract/sdk/rs/src/commands/device/delete.rs b/smartcontract/sdk/rs/src/commands/device/delete.rs index 7a6f9482d6..008a8c9bd7 100644 --- a/smartcontract/sdk/rs/src/commands/device/delete.rs +++ b/smartcontract/sdk/rs/src/commands/device/delete.rs @@ -128,7 +128,6 @@ mod tests { desired_status: DeviceDesiredStatus::Drained, device_health: DeviceHealth::Unknown, interfaces: vec![], - new_interfaces: vec![], unicast_users_count: 0, multicast_subscribers_count: 0, max_unicast_users: 0, @@ -136,6 +135,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() } } diff --git a/smartcontract/sdk/rs/src/commands/device/interface/create.rs b/smartcontract/sdk/rs/src/commands/device/interface/create.rs index 8fea9ce06b..4fdd464e3a 100644 --- a/smartcontract/sdk/rs/src/commands/device/interface/create.rs +++ b/smartcontract/sdk/rs/src/commands/device/interface/create.rs @@ -120,7 +120,6 @@ mod tests { dz_prefixes: "10.0.0.1/24".parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -132,6 +131,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() } } diff --git a/smartcontract/sdk/rs/src/commands/device/interface/delete.rs b/smartcontract/sdk/rs/src/commands/device/interface/delete.rs index 174c16e7c7..98bfe0ee13 100644 --- a/smartcontract/sdk/rs/src/commands/device/interface/delete.rs +++ b/smartcontract/sdk/rs/src/commands/device/interface/delete.rs @@ -94,7 +94,6 @@ mod tests { dz_prefixes: "10.0.0.1/24".parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -106,6 +105,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() } } diff --git a/smartcontract/sdk/rs/src/commands/device/interface/remove.rs b/smartcontract/sdk/rs/src/commands/device/interface/remove.rs index cc72356f99..963d8d7146 100644 --- a/smartcontract/sdk/rs/src/commands/device/interface/remove.rs +++ b/smartcontract/sdk/rs/src/commands/device/interface/remove.rs @@ -84,7 +84,6 @@ mod tests { dz_prefixes: "10.0.0.1/24".parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -96,6 +95,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client diff --git a/smartcontract/sdk/rs/src/commands/device/interface/update.rs b/smartcontract/sdk/rs/src/commands/device/interface/update.rs index 8969e946a8..186a6974e4 100644 --- a/smartcontract/sdk/rs/src/commands/device/interface/update.rs +++ b/smartcontract/sdk/rs/src/commands/device/interface/update.rs @@ -117,7 +117,6 @@ mod tests { dz_prefixes: "10.0.0.1/24".parse().unwrap(), mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 255, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -129,6 +128,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() } } diff --git a/smartcontract/sdk/rs/src/commands/device/sethealth.rs b/smartcontract/sdk/rs/src/commands/device/sethealth.rs index 2824906ebf..c5ae3b41a5 100644 --- a/smartcontract/sdk/rs/src/commands/device/sethealth.rs +++ b/smartcontract/sdk/rs/src/commands/device/sethealth.rs @@ -73,7 +73,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 250, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -85,6 +84,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -150,7 +150,6 @@ mod tests { metrics_publisher: None, mgmt_vrf: Some("mgmt".to_string()), location_pk: None, - interfaces: None, max_users: None, users_count: None, status: None, diff --git a/smartcontract/sdk/rs/src/commands/device/update.rs b/smartcontract/sdk/rs/src/commands/device/update.rs index be5e60adff..fba38fed37 100644 --- a/smartcontract/sdk/rs/src/commands/device/update.rs +++ b/smartcontract/sdk/rs/src/commands/device/update.rs @@ -11,10 +11,7 @@ use doublezero_serviceability::{ pda::get_resource_extension_pda, processors::device::update::DeviceUpdateArgs, resource::ResourceType, - state::{ - device::{DeviceDesiredStatus, DeviceStatus, DeviceType}, - interface::Interface, - }, + state::device::{DeviceDesiredStatus, DeviceStatus, DeviceType}, }; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature}; use std::net::Ipv4Addr; @@ -30,7 +27,6 @@ pub struct UpdateDeviceCommand { pub contributor_pk: Option, pub location_pk: Option, pub mgmt_vrf: Option, - pub interfaces: Option>, pub max_users: Option, pub users_count: Option, pub status: Option, @@ -173,7 +169,6 @@ mod tests { owner: pda_pubkey, mgmt_vrf: "default".to_string(), interfaces: vec![], - new_interfaces: vec![], max_users: 250, users_count: 0, device_health: DeviceHealth::ReadyForUsers, @@ -185,6 +180,7 @@ mod tests { reserved_seats: 0, multicast_publishers_count: 0, max_multicast_publishers: 0, + ..Default::default() }; client @@ -250,7 +246,6 @@ mod tests { metrics_publisher: None, mgmt_vrf: Some("mgmt".to_string()), location_pk: None, - interfaces: None, max_users: None, users_count: None, status: None, diff --git a/smartcontract/sdk/rs/src/commands/topology/create.rs b/smartcontract/sdk/rs/src/commands/topology/create.rs index 68c5fd58ac..75df31d738 100644 --- a/smartcontract/sdk/rs/src/commands/topology/create.rs +++ b/smartcontract/sdk/rs/src/commands/topology/create.rs @@ -66,7 +66,7 @@ impl CreateTopologyCommand { .into_iter() .filter(|(_, device)| { device - .new_interfaces + .interfaces .iter() .any(|i| i.loopback_type == LoopbackType::Vpnv4) }) @@ -105,7 +105,7 @@ mod tests { accountdata::AccountData, accounttype::AccountType, device::Device, - interface::{LoopbackType, NewInterface}, + interface::{Interface, LoopbackType}, topology::TopologyConstraint, }, }; @@ -173,7 +173,7 @@ mod tests { let vpnv4_device_pk = Pubkey::new_unique(); let vpnv4_device = Device { - new_interfaces: vec![NewInterface { + interfaces: vec![Interface { loopback_type: LoopbackType::Vpnv4, ..Default::default() }], @@ -182,7 +182,7 @@ mod tests { let other_device_pk = Pubkey::new_unique(); let other_device = Device { - new_interfaces: vec![NewInterface { + interfaces: vec![Interface { loopback_type: LoopbackType::None, ..Default::default() }], diff --git a/smartcontract/sdk/rs/src/lib.rs b/smartcontract/sdk/rs/src/lib.rs index 2a5454dd73..622b9955bb 100644 --- a/smartcontract/sdk/rs/src/lib.rs +++ b/smartcontract/sdk/rs/src/lib.rs @@ -20,10 +20,7 @@ pub use doublezero_serviceability::{ exchange::{Exchange, ExchangeStatus, BGP_COMMUNITY_MAX, BGP_COMMUNITY_MIN}, globalconfig::GlobalConfig, globalstate::GlobalState, - interface::{ - CurrentInterfaceVersion, Interface, InterfaceStatus, InterfaceType, LoopbackType, - NewInterface, - }, + interface::{Interface, InterfaceDeprecated, InterfaceStatus, InterfaceType, LoopbackType}, link::{Link, LinkLinkType, LinkStatus}, location::{Location, LocationStatus}, multicastgroup::{MulticastGroup, MulticastGroupStatus}, From 258ac7e292cfb2c059d341f911d7310f909d640c Mon Sep 17 00:00:00 2001 From: Greg Mitchell Date: Wed, 6 May 2026 19:59:28 +0000 Subject: [PATCH 2/3] serviceability: remove InterfaceV3 entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V3 was added by an earlier change, never written to production accounts, and reverted in #3653. After #3667 stopped producing V3 (always-V2 projection of the legacy slot) and #3675 deleted the migrate processor, the type is dead weight. This removes it as if it never existed — no read shim, since no on-chain account ever held discriminant 3. - Delete InterfaceV3 struct + Default/From/TryFrom impls. - Delete InterfaceDeprecated::V3 variant; discriminant 3 is now an unused reserved slot. Unknown discriminants fall through to InterfaceV2::default(). - Drop V3 match arms in InterfaceDeprecated::to_v2 / size and in Device::TryFrom legacy-rebuild. - Drop V3 from Go/Python/TS SDKs: remove DeserializeInterfaceV3, version==3 / version === 3 branches, and the V3 cross-language Go test. - Delete the V3 cross-language byte-layout debug test in interface.rs and the V3 block in test_interface_version. On-disk write format unchanged. --- CHANGELOG.md | 4 + .../python/serviceability/state.py | 23 +- .../typescript/serviceability/state.ts | 22 +- .../src/state/device.rs | 7 +- .../src/state/interface.rs | 294 +----------------- .../sdk/go/serviceability/bytereader_test.go | 104 ------- .../sdk/go/serviceability/deserialize.go | 27 +- smartcontract/sdk/go/serviceability/state.go | 6 +- 8 files changed, 33 insertions(+), 454 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e422fd4e5..b32a7d2f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ All notable changes to this project will be documented in this file. - SDK - Go, Python, and TypeScript serviceability readers parse the trailing `new_interfaces` vec on `Device` with size-prefixed (u16 size + u8 version + body) forward-compat framing. Empty trailing falls back to rebuilding `new_interfaces` from the legacy enum vec, matching the Rust device reader. Length mismatch between the legacy and trailing vecs is surfaced as an error (Python/TS raise; Go sets `Device.DeserializeError`). Bumps `CURRENT_INTERFACE_VERSION` / `CurrentInterfaceVersion` to `4` across SDKs to match Rust's `CURRENT_INTERFACE_SCHEMA_VERSION` ([#3660](https://github.com/malbeclabs/doublezero/issues/3660)) - Regenerate `device.{bin,json}` through Device's custom serializer with a populated `new_interfaces` vec (one Vpnv4 loopback carrying a `FlexAlgoNodeSegment`, one physical user-tunnel-endpoint), and add `device_legacy.{bin,json}` (legacy `interfaces` vec only, no trailing bytes — exercises the SDK legacy-fallback path) and `device_future_version.{bin,json}` (last trailing-vec element doctored to `version=5` with 8 trailing junk bytes — exercises the SDK skip-to-end path). Adds fixture-driven Go SDK tests; extends the existing Python/TS fixture tests to cover all three Device fixtures ([#3661](https://github.com/malbeclabs/doublezero/issues/3661)) +- Smartcontract + - Delete `InterfaceV3` and the `InterfaceDeprecated::V3` variant from the serviceability program. V3 was added by an earlier change, never written to production accounts, and reverted in #3653 / no longer produced after #3667; this removes the dead type. Discriminant 3 is now an unused reserved slot in `InterfaceDeprecated`'s encoding space — unknown discriminants fall through to `InterfaceV2::default()`. Removes the V3 struct, its helper impls (`From`, `TryFrom<&InterfaceV1>`, `Default`, `TryFrom<&InterfaceV3> for InterfaceV2`), V3 match arms in `InterfaceDeprecated::to_v2`/`size`/`Device::TryFrom`, and the V3 cross-language byte-layout debug test. On-disk write format is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) +- SDK + - Drop V3 handling from the Go, Python, and TypeScript serviceability readers: remove `DeserializeInterfaceV3` (Go) and the `version === 3` / `version == 3` legacy-slot branches (Python/TS); remove the `TestDeserializeInterfaceV3CrossLanguage` Go test. The forward-compat trailing `interfaces` vec continues to carry `flex_algo_node_segments` via the size-prefixed body — that path is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) ## [v0.21.0](https://github.com/malbeclabs/doublezero/compare/client/v0.20.0...client/v0.21.0) - 2026-05-01 diff --git a/sdk/serviceability/python/serviceability/state.py b/sdk/serviceability/python/serviceability/state.py index 521cf483e6..9ddc39d6b7 100644 --- a/sdk/serviceability/python/serviceability/state.py +++ b/sdk/serviceability/python/serviceability/state.py @@ -404,9 +404,9 @@ def __str__(self) -> str: # On-wire schema version for the size-prefixed Interface format # (matches Rust's CURRENT_INTERFACE_SCHEMA_VERSION). Note: prior to issue #3660 # this constant gated the legacy enum reader at value 2 (max known disc=1); it -# is now bumped to 4 to match the size-prefixed schema. Legacy enum reads still -# only handle version 0 (V1) and 1 (V2); version 3 (V3) accounts fall through -# to a default Interface (pre-existing gap). +# is now bumped to 4 to match the size-prefixed schema. Legacy enum reads only +# handle version 0 (V1) and 1 (V2); discriminant 3 was a transient V3 format +# that never reached production and is no longer recognized. CURRENT_INTERFACE_VERSION = 4 @@ -445,8 +445,8 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.version = r.read_u8() if iface.version > CURRENT_INTERFACE_VERSION - 1: return iface - # Discriminants: 0=V1, 1 or 2=V2 (no flex_algo_node_segments), - # 3=V3 (V2 fields + flex_algo_node_segments). + # Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was V3 (transient, + # never shipped) and is intentionally unhandled. if iface.version == 0: iface.status = InterfaceStatus(r.read_u8()) iface.name = r.read_string() @@ -456,7 +456,7 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.ip_net = r.read_network_v4() iface.node_segment_idx = r.read_u16() iface.user_tunnel_endpoint = r.read_bool() - elif iface.version in (1, 2, 3): + elif iface.version in (1, 2): iface.status = InterfaceStatus(r.read_u8()) iface.name = r.read_string() iface.interface_type = InterfaceType(r.read_u8()) @@ -471,13 +471,6 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.ip_net = r.read_network_v4() iface.node_segment_idx = r.read_u16() iface.user_tunnel_endpoint = r.read_bool() - if iface.version == 3: - count = r.read_u32() - for _ in range(count): - seg = FlexAlgoNodeSegment() - seg.topology = _read_pubkey(r) - seg.node_segment_idx = r.read_u16() - iface.flex_algo_node_segments.append(seg) return iface @classmethod @@ -493,8 +486,8 @@ def from_reader_sized(cls, r: DefensiveReader) -> Interface: iface.size = r.read_u16() iface.version = r.read_u8() - # Body fields (current schema, version 4): same order as InterfaceV2 + - # the flex_algo_node_segments vec from V3. + # Body fields (current schema, version 4): same order as InterfaceV2, + # plus a trailing flex_algo_node_segments vec. iface.status = InterfaceStatus(r.read_u8()) iface.name = r.read_string() iface.interface_type = InterfaceType(r.read_u8()) diff --git a/sdk/serviceability/typescript/serviceability/state.ts b/sdk/serviceability/typescript/serviceability/state.ts index 85cdb93ed6..bd74066aff 100644 --- a/sdk/serviceability/typescript/serviceability/state.ts +++ b/sdk/serviceability/typescript/serviceability/state.ts @@ -530,8 +530,8 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { return iface; } - // Discriminants: 0=V1, 1 or 2=V2 (no flex_algo_node_segments), - // 3=V3 (V2 fields + flex_algo_node_segments). + // Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was V3 (transient, + // never shipped) and is intentionally unhandled. if (iface.version === 0) { iface.status = r.readU8(); iface.name = r.readString(); @@ -541,7 +541,7 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { iface.ipNet = r.readNetworkV4(); iface.nodeSegmentIdx = r.readU16(); iface.userTunnelEndpoint = r.readBool(); - } else if (iface.version === 1 || iface.version === 2 || iface.version === 3) { + } else if (iface.version === 1 || iface.version === 2) { iface.status = r.readU8(); iface.name = r.readString(); iface.interfaceType = r.readU8(); @@ -556,18 +556,6 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { iface.ipNet = r.readNetworkV4(); iface.nodeSegmentIdx = r.readU16(); iface.userTunnelEndpoint = r.readBool(); - if (iface.version === 3) { - const segCount = r.readU32(); - const flexAlgoNodeSegments: FlexAlgoNodeSegment[] = []; - for (let i = 0; i < segCount; i++) { - if (r.remaining < 34) break; // 32 (pubkey) + 2 (u16) - flexAlgoNodeSegments.push({ - topology: readPubkey(r), - nodeSegmentIdx: r.readU16(), - }); - } - iface.flexAlgoNodeSegments = flexAlgoNodeSegments; - } } return iface; @@ -583,8 +571,8 @@ function deserializeInterfaceSized(r: DefensiveReader): DeviceInterface { const size = r.readU16(); const version = r.readU8(); - // Body fields (current schema, version 4): same order as InterfaceV2 + the - // flex_algo_node_segments vec from V3. + // Body fields (current schema, version 4): same order as InterfaceV2, plus + // a trailing flex_algo_node_segments vec. const status = r.readU8(); const name = r.readString(); const interfaceType = r.readU8(); diff --git a/smartcontract/programs/doublezero-serviceability/src/state/device.rs b/smartcontract/programs/doublezero-serviceability/src/state/device.rs index 8ff04f988c..4da3c1bc07 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/device.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/device.rs @@ -603,18 +603,13 @@ impl TryFrom<&[u8]> for Device { let interfaces = if trailing.is_empty() { // Legacy account: rebuild from the legacy enum vec via per-variant - // `TryFrom`. V3 is projected through V2, dropping `flex_algo_node_segments` - // (V3 only exists from migrate/backfill paths post-#3653). + // `TryFrom`. deprecated_interfaces .iter() .map(|iface| -> Result { match iface { InterfaceDeprecated::V1(v1) => v1.try_into(), InterfaceDeprecated::V2(v2) => v2.try_into(), - InterfaceDeprecated::V3(v3) => { - let v2: InterfaceV2 = v3.try_into()?; - (&v2).try_into() - } } }) .collect::, _>>()? diff --git a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs index c271a155d3..c6138f8fd7 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs @@ -373,29 +373,6 @@ impl TryFrom<&InterfaceV1> for InterfaceV2 { } } -impl TryFrom<&InterfaceV3> for InterfaceV2 { - type Error = ProgramError; - - fn try_from(data: &InterfaceV3) -> Result { - Ok(Self { - status: data.status, - name: data.name.clone(), - interface_type: data.interface_type, - interface_cyoa: data.interface_cyoa, - interface_dia: data.interface_dia, - loopback_type: data.loopback_type, - bandwidth: data.bandwidth, - cir: data.cir, - mtu: data.mtu, - routing_mode: data.routing_mode, - vlan_id: data.vlan_id, - ip_net: data.ip_net, - node_segment_idx: data.node_segment_idx, - user_tunnel_endpoint: data.user_tunnel_endpoint, - }) - } -} - impl Default for InterfaceV2 { fn default() -> Self { Self { @@ -417,77 +394,6 @@ impl Default for InterfaceV2 { } } -#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct InterfaceV3 { - pub status: InterfaceStatus, - pub name: String, - pub interface_type: InterfaceType, - pub interface_cyoa: InterfaceCYOA, - pub interface_dia: InterfaceDIA, - pub loopback_type: LoopbackType, - pub bandwidth: u64, - pub cir: u64, - pub mtu: u16, - pub routing_mode: RoutingMode, - pub vlan_id: u16, - pub ip_net: NetworkV4, - pub node_segment_idx: u16, - pub user_tunnel_endpoint: bool, - pub flex_algo_node_segments: Vec, -} - -impl InterfaceV3 { - pub fn size(&self) -> usize { - Self::size_given_name_len(self.name.len()) - } - - pub fn to_interface(&self) -> InterfaceDeprecated { - InterfaceDeprecated::V3(self.clone()) - } - - pub fn size_given_name_len(name_len: usize) -> usize { - 1 + 4 + name_len + 1 + 1 + 1 + 1 + 8 + 8 + 2 + 1 + 2 + 5 + 2 + 1 + 4 // +4 for empty flex_algo_node_segments vec (Borsh length prefix) - } -} - -impl From for InterfaceV3 { - fn from(v2: InterfaceV2) -> Self { - Self { - status: v2.status, - name: v2.name, - interface_type: v2.interface_type, - interface_cyoa: v2.interface_cyoa, - interface_dia: v2.interface_dia, - loopback_type: v2.loopback_type, - bandwidth: v2.bandwidth, - cir: v2.cir, - mtu: v2.mtu, - routing_mode: v2.routing_mode, - vlan_id: v2.vlan_id, - ip_net: v2.ip_net, - node_segment_idx: v2.node_segment_idx, - user_tunnel_endpoint: v2.user_tunnel_endpoint, - flex_algo_node_segments: vec![], - } - } -} - -impl TryFrom<&InterfaceV1> for InterfaceV3 { - type Error = ProgramError; - - fn try_from(data: &InterfaceV1) -> Result { - let v2: InterfaceV2 = data.try_into()?; - Ok(v2.into()) - } -} - -impl Default for InterfaceV3 { - fn default() -> Self { - InterfaceV2::default().into() - } -} - #[repr(u8)] #[derive(BorshSerialize, Debug, PartialEq, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -495,10 +401,10 @@ impl Default for InterfaceV3 { pub enum InterfaceDeprecated { V1(InterfaceV1) = 0, /// Discriminant 1: V2 format. Does NOT include flex_algo_node_segments. + /// Discriminant 2 is reserved (never written). Discriminant 3 was a transient + /// V3 format that never reached production and has been removed; the slot is + /// reserved and unused. V2(InterfaceV2) = 1, - /// Discriminant 3: V3 format. Includes flex_algo_node_segments (RFC-18). - /// Discriminant 2 is intentionally skipped (reserved). - V3(InterfaceV3) = 3, } impl borsh::BorshDeserialize for InterfaceDeprecated { @@ -511,23 +417,19 @@ impl borsh::BorshDeserialize for InterfaceDeprecated { 1 | 2 => Ok(InterfaceDeprecated::V2( borsh::BorshDeserialize::deserialize_reader(reader)?, )), - 3 => Ok(InterfaceDeprecated::V3( - borsh::BorshDeserialize::deserialize_reader(reader)?, - )), - _ => Ok(InterfaceDeprecated::V3(InterfaceV3::default())), + _ => Ok(InterfaceDeprecated::V2(InterfaceV2::default())), } } } impl InterfaceDeprecated { - /// Convert any legacy variant to its V2 projection. V1 and V3 fan in via - /// `TryFrom<&InterfaceVN>` for `InterfaceV2`; conversion failures fall back + /// Convert any legacy variant to its V2 projection. V1 fans in via + /// `TryFrom<&InterfaceV1>` for `InterfaceV2`; conversion failures fall back /// to `InterfaceV2::default()`. pub fn to_v2(&self) -> InterfaceV2 { match self { InterfaceDeprecated::V1(v1) => v1.try_into().unwrap_or_default(), InterfaceDeprecated::V2(v2) => v2.clone(), - InterfaceDeprecated::V3(v3) => v3.try_into().unwrap_or_default(), } } @@ -535,7 +437,6 @@ impl InterfaceDeprecated { let base_size = match self { InterfaceDeprecated::V1(v1) => v1.size(), InterfaceDeprecated::V2(v2) => v2.size(), - InterfaceDeprecated::V3(v3) => v3.size(), }; base_size + 1 // +1 for the enum discriminant } @@ -872,38 +773,6 @@ fn test_interface_version() { assert_eq!(iface_v2.ip_net, "10.0.0.0/24".parse().unwrap()); assert_eq!(iface_v2.node_segment_idx, 200); assert!(iface_v2.user_tunnel_endpoint); - - let iface = InterfaceV3 { - status: InterfaceStatus::Activated, - name: "Loopback0".to_string(), - interface_type: InterfaceType::Loopback, - interface_cyoa: InterfaceCYOA::GREOverDIA, - interface_dia: InterfaceDIA::DIA, - loopback_type: LoopbackType::Ipv4, - bandwidth: 1000, - cir: 500, - mtu: 1500, - routing_mode: RoutingMode::BGP, - vlan_id: 100, - ip_net: "10.0.0.0/24".parse().unwrap(), - node_segment_idx: 200, - user_tunnel_endpoint: true, - flex_algo_node_segments: vec![], - } - .to_interface(); - - assert!( - matches!(iface, InterfaceDeprecated::V3(_)), - "iface is not InterfaceDeprecated::V3" - ); - let iface_v3 = iface.to_v2(); - assert_eq!(iface_v3.name, "Loopback0"); - assert_eq!(iface_v3.interface_type, InterfaceType::Loopback); - assert_eq!(iface_v3.loopback_type, LoopbackType::Ipv4); - assert_eq!(iface_v3.vlan_id, 100); - assert_eq!(iface_v3.ip_net, "10.0.0.0/24".parse().unwrap()); - assert_eq!(iface_v3.node_segment_idx, 200); - assert!(iface_v3.user_tunnel_endpoint); } #[cfg(test)] @@ -1011,157 +880,6 @@ mod test_interface_validate { let err = InterfaceDeprecated::V2(iface).validate(); assert_eq!(err.unwrap_err(), DoubleZeroError::InvalidInterfaceIp); } - - /// Test that prints serialized bytes of InterfaceV3 for cross-language debugging. - /// Run with: cargo test test_interface_v3_serialization_bytes -- --nocapture - #[test] - fn test_interface_v3_serialization_bytes() { - // Create an interface similar to what the e2e test creates after update - let iface = InterfaceV3 { - status: InterfaceStatus::Activated, - name: "Loopback106".to_string(), - interface_type: InterfaceType::Loopback, - interface_cyoa: InterfaceCYOA::None, - interface_dia: InterfaceDIA::None, - loopback_type: LoopbackType::Ipv4, // Updated value - bandwidth: 0, - cir: 0, - mtu: 9000, // Updated value - routing_mode: RoutingMode::Static, - vlan_id: 0, - ip_net: "203.0.113.40/32".parse().unwrap(), - node_segment_idx: 0, - user_tunnel_endpoint: true, - flex_algo_node_segments: vec![], - }; - - // Serialize as InterfaceDeprecated::V3 (with enum discriminant) - let interface_enum = InterfaceDeprecated::V3(iface.clone()); - let bytes = borsh::to_vec(&interface_enum).unwrap(); - - println!("\n=== InterfaceV3 Serialization Debug ==="); - println!("Total bytes: {}", bytes.len()); - println!("Hex: {:02x?}", bytes); - println!("\nField breakdown:"); - println!(" [0] enum discriminant (V3=3): {:02x}", bytes[0]); - - let mut offset = 1; - println!(" [{}] status (Activated=1): {:02x}", offset, bytes[offset]); - offset += 1; - - // String: 4 bytes length + chars - let name_len = u32::from_le_bytes([ - bytes[offset], - bytes[offset + 1], - bytes[offset + 2], - bytes[offset + 3], - ]); - println!( - " [{}-{}] name length: {} (0x{:08x})", - offset, - offset + 3, - name_len, - name_len - ); - offset += 4; - let name_bytes = &bytes[offset..offset + name_len as usize]; - println!( - " [{}-{}] name: {:?}", - offset, - offset + name_len as usize - 1, - String::from_utf8_lossy(name_bytes) - ); - offset += name_len as usize; - - println!( - " [{}] interface_type (Loopback=2): {:02x}", - offset, bytes[offset] - ); - offset += 1; - println!( - " [{}] interface_cyoa (None=0): {:02x}", - offset, bytes[offset] - ); - offset += 1; - println!( - " [{}] interface_dia (None=0): {:02x}", - offset, bytes[offset] - ); - offset += 1; - println!( - " [{}] loopback_type (Ipv4=1): {:02x}", - offset, bytes[offset] - ); - offset += 1; - - let bandwidth = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap()); - println!( - " [{}-{}] bandwidth: {} (0x{:016x})", - offset, - offset + 7, - bandwidth, - bandwidth - ); - offset += 8; - - let cir = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap()); - println!( - " [{}-{}] cir: {} (0x{:016x})", - offset, - offset + 7, - cir, - cir - ); - offset += 8; - - let mtu = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap()); - println!(" [{}-{}] mtu: {} (0x{:04x})", offset, offset + 1, mtu, mtu); - offset += 2; - - println!( - " [{}] routing_mode (Static=0): {:02x}", - offset, bytes[offset] - ); - offset += 1; - - let vlan_id = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap()); - println!( - " [{}-{}] vlan_id: {} (0x{:04x})", - offset, - offset + 1, - vlan_id, - vlan_id - ); - offset += 2; - - println!( - " [{}-{}] ip_net: {:02x?}", - offset, - offset + 4, - &bytes[offset..offset + 5] - ); - offset += 5; - - let node_segment_idx = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap()); - println!( - " [{}-{}] node_segment_idx: {} (0x{:04x})", - offset, - offset + 1, - node_segment_idx, - node_segment_idx - ); - offset += 2; - - println!(" [{}] user_tunnel_endpoint: {:02x}", offset, bytes[offset]); - offset += 1; - - println!(" Total parsed: {} bytes", offset); - println!("=====================================\n"); - - // Verify the serialization - assert_eq!(mtu, 9000); - assert_eq!(bytes[0], 3); // V3 discriminant - } } #[cfg(test)] diff --git a/smartcontract/sdk/go/serviceability/bytereader_test.go b/smartcontract/sdk/go/serviceability/bytereader_test.go index 428ace76a5..b52a9e91d8 100644 --- a/smartcontract/sdk/go/serviceability/bytereader_test.go +++ b/smartcontract/sdk/go/serviceability/bytereader_test.go @@ -2,7 +2,6 @@ package serviceability import ( "encoding/binary" - "encoding/hex" "reflect" "testing" ) @@ -249,109 +248,6 @@ func TestReadString(t *testing.T) { } } -// TestDeserializeInterfaceV2CrossLanguage tests deserializing bytes that Rust produces. -// To get the expected bytes, run in Rust: -// -// cargo test test_interface_v2_serialization_bytes -- --nocapture -// -// Then copy the hex output here. -func TestDeserializeInterfaceV3CrossLanguage(t *testing.T) { - t.Parallel() - - // These bytes are ACTUAL output from Rust test (RFC-18, V3 with flex_algo_node_segments): - // Hex: [03, 03, 0b, 00, 00, 00, 4c, 6f, 6f, 70, 62, 61, 63, 6b, 31, 30, 36, 01, 00, 00, 02, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 28, 23, 00, 00, 00, cb, 00, 71, 28, 20, 00, 00, 01, 00, 00, 00, 00] - // - // Field breakdown from Rust: - // [0] enum discriminant (V3=3): 03 - // [1] status (Activated=3): 03 - // [2-5] name length: 11 (0x0000000b) - // [6-16] name: "Loopback106" - // [17] interface_type (Loopback=1): 01 - // [18] interface_cyoa (None=0): 00 - // [19] interface_dia (None=0): 00 - // [20] loopback_type (Ipv4=2): 02 - // [21-28] bandwidth: 0 - // [29-36] cir: 0 - // [37-38] mtu: 9000 (0x2328) - // [39] routing_mode (Static=0): 00 - // [40-41] vlan_id: 0 - // [42-46] ip_net: [cb, 00, 71, 28, 20] = 203.0.113.40/32 - // [47-48] node_segment_idx: 0 - // [49] user_tunnel_endpoint: 01 (true) - // [50-53] flex_algo_node_segments length: 0 (empty vec) - - // Use EXACT bytes from Rust serialization - data := []byte{ - 0x03, // [0] enum discriminant V3=3 - 0x03, // [1] status Activated=3 - 0x0b, 0x00, 0x00, 0x00, // [2-5] name length = 11 - 0x4c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x31, 0x30, 0x36, // [6-16] "Loopback106" - 0x01, // [17] interface_type Loopback=1 - 0x00, // [18] interface_cyoa None=0 - 0x00, // [19] interface_dia None=0 - 0x02, // [20] loopback_type Ipv4=2 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [21-28] bandwidth=0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [29-36] cir=0 - 0x28, 0x23, // [37-38] mtu=9000 - 0x00, // [39] routing_mode Static=0 - 0x00, 0x00, // [40-41] vlan_id=0 - 0xcb, 0x00, 0x71, 0x28, 0x20, // [42-46] ip_net 203.0.113.40/32 - 0x00, 0x00, // [47-48] node_segment_idx=0 - 0x01, // [49] user_tunnel_endpoint=true - 0x00, 0x00, 0x00, 0x00, // [50-53] flex_algo_node_segments length=0 (empty vec, RFC-18) - } - - t.Logf("Test data (%d bytes): %s", len(data), hex.EncodeToString(data)) - - reader := NewByteReader(data) - var iface Interface - DeserializeInterface(reader, &iface) - - t.Logf("Deserialized interface:") - t.Logf(" Version: %d", iface.Version) - t.Logf(" Status: %d", iface.Status) - t.Logf(" Name: %s", iface.Name) - t.Logf(" InterfaceType: %d", iface.InterfaceType) - t.Logf(" InterfaceCYOA: %d", iface.InterfaceCYOA) - t.Logf(" InterfaceDIA: %d", iface.InterfaceDIA) - t.Logf(" LoopbackType: %d", iface.LoopbackType) - t.Logf(" Bandwidth: %d", iface.Bandwidth) - t.Logf(" Cir: %d", iface.Cir) - t.Logf(" Mtu: %d", iface.Mtu) - t.Logf(" RoutingMode: %d", iface.RoutingMode) - t.Logf(" VlanId: %d", iface.VlanId) - t.Logf(" IpNet: %v", iface.IpNet) - t.Logf(" NodeSegmentIdx: %d", iface.NodeSegmentIdx) - t.Logf(" UserTunnelEndpoint: %v", iface.UserTunnelEndpoint) - t.Logf(" Remaining bytes: %d", reader.Remaining()) - - // Assertions - if iface.Version != 3 { - t.Errorf("Version: got %d, expected 3 (V3 enum discriminant)", iface.Version) - } - if iface.Status != InterfaceStatusActivated { - t.Errorf("Status: got %d, expected %d (Activated)", iface.Status, InterfaceStatusActivated) - } - if iface.Name != "Loopback106" { - t.Errorf("Name: got %s, expected Loopback106", iface.Name) - } - if iface.InterfaceType != InterfaceTypeLoopback { - t.Errorf("InterfaceType: got %d, expected %d (Loopback)", iface.InterfaceType, InterfaceTypeLoopback) - } - if iface.LoopbackType != LoopbackTypeIpv4 { - t.Errorf("LoopbackType: got %d, expected %d (Ipv4)", iface.LoopbackType, LoopbackTypeIpv4) - } - if iface.Mtu != 9000 { - t.Errorf("Mtu: got %d, expected 9000", iface.Mtu) - } - if !iface.UserTunnelEndpoint { - t.Errorf("UserTunnelEndpoint: got %v, expected true", iface.UserTunnelEndpoint) - } - if reader.Remaining() != 0 { - t.Errorf("Should have consumed all bytes, but %d remaining", reader.Remaining()) - } -} - // TestDeserializeResourceExtensionIdAllocator tests deserializing a ResourceExtension with ID allocator func TestDeserializeResourceExtensionIdAllocator(t *testing.T) { t.Parallel() diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index f07fc4f3de..f1f5b7dab5 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -88,24 +88,20 @@ func DeserializeContributor(reader *ByteReader, contributor *Contributor) { // Interface version history (discriminant byte): // // 0 — V1: original format (no CYOA/DIA/Bandwidth fields) -// 1 — V2: adds CYOA, DIA, Bandwidth, Cir, Mtu, RoutingMode (no flex_algo_node_segments) +// 1 — V2: adds CYOA, DIA, Bandwidth, Cir, Mtu, RoutingMode // 2 — reserved, never written -// 3 — V3: V2 fields + flex_algo_node_segments (RFC-18) // // The on-chain Device serializer projects the legacy deprecated_interfaces slot as V2 -// (per #3653); discriminant-3 entries seen here are residual from older -// accounts. Newer flex_algo_node_segments data lives in the trailing -// interfaces vec on Device, not in this slot. +// (per #3653). flex_algo_node_segments lives only in the trailing forward-compat +// interfaces vec on Device (read via DeserializeInterfaceSized), not in this slot. func DeserializeInterface(reader *ByteReader, iface *Interface) { iface.Version = reader.ReadU8() switch iface.Version { case 0: // V1 DeserializeInterfaceV1(reader, iface) - case 1, 2: // V2: no flex_algo_node_segments + case 1, 2: // V2 DeserializeInterfaceV2(reader, iface) - case 3: // V3: includes flex_algo_node_segments (RFC-18) - DeserializeInterfaceV3(reader, iface) default: log.Println("DeserializeInterface: Unsupported interface version", iface.Version) } @@ -139,17 +135,6 @@ func DeserializeInterfaceV2(reader *ByteReader, iface *Interface) { iface.UserTunnelEndpoint = (reader.ReadU8() != 0) } -func DeserializeInterfaceV3(reader *ByteReader, iface *Interface) { - DeserializeInterfaceV2(reader, iface) - // flex_algo_node_segments (RFC-18): present in all V3 accounts. - length := reader.ReadU32() - iface.FlexAlgoNodeSegments = make([]FlexAlgoNodeSegment, length) - for i := uint32(0); i < length; i++ { - iface.FlexAlgoNodeSegments[i].Topology = reader.ReadPubkey() - iface.FlexAlgoNodeSegments[i].NodeSegmentIdx = reader.ReadU16() - } -} - // DeserializeInterfaceSized reads a single size-prefixed Interface element. // // Wire format: u16 size + u8 version + body, where size includes the 3-byte prefix. @@ -160,8 +145,8 @@ func DeserializeInterfaceSized(reader *ByteReader, iface *Interface) { iface.Size = reader.ReadU16() iface.Version = reader.ReadU8() - // Body fields (current schema, version 4): same order as InterfaceV2 + the - // flex_algo_node_segments vec from V3. + // Body fields (current schema, version 4): same order as InterfaceV2, plus a + // trailing flex_algo_node_segments vec. iface.Status = InterfaceStatus(reader.ReadU8()) iface.Name = reader.ReadString() iface.InterfaceType = InterfaceType(reader.ReadU8()) diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index d8de6b745c..e3cb81cd7a 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -415,9 +415,9 @@ type Interface struct { NodeSegmentIdx uint16 UserTunnelEndpoint bool // FlexAlgoNodeSegments holds flex-algo node segment assignments for this interface (RFC-18). - // Only populated when reading a discriminant-3 (V3) interface; the on-chain Device serializer - // now projects the deprecated_interfaces slot as V2 (per #3653) and surfaces segments through - // the trailing interfaces vec on Device. Nil otherwise. + // Populated only on entries read from the trailing forward-compat interfaces vec on Device + // (via DeserializeInterfaceSized). The legacy deprecated_interfaces slot does not carry + // segments; nil there. FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` } From 5aa56fba9d88df6de8e974283a052b0bab1f9fc4 Mon Sep 17 00:00:00 2001 From: Greg Mitchell Date: Thu, 7 May 2026 04:30:08 +0000 Subject: [PATCH 3/3] serviceability: read legacy V3 bytes as V2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mainnet-beta has one device (la2r-dz01, interface Ethernet20/1) with a discriminant-3 (V3) entry in its legacy deprecated_interfaces slot, written by an admin path that no longer exists. The previous commit removed all V3 read handling, which caused byte misalignment on that account: the V3 body (V2 fields + flex_algo_node_segments vec) went unread, the cursor desynced, and downstream Device fields decoded as garbage. CI sdk-compat-test caught this against mainnet: "110 is not a valid DeviceHealth". Restore byte-level consumption of discriminant 3 in all readers (Rust + Go/Python/TS SDKs): read the V2 body and consume + drop the trailing flex_algo_node_segments vec, then surface the entry as V2. The InterfaceV3 struct stays deleted; only the wire-compat read path is restored. Segments live in the trailing forward-compat interfaces vec on Device post-#3667 — this slot loses them, matching the always-V2 projection on writes. Adds a Rust regression test that hand-crafts a Vec with a V3-encoded element followed by a V2 element and asserts the V3 bytes are fully consumed, so the trailing V2 still decodes — guards against the misalignment that broke CI. --- CHANGELOG.md | 4 +- .../python/serviceability/state.py | 13 +++- .../typescript/serviceability/state.ts | 16 +++- .../src/state/interface.rs | 78 ++++++++++++++++++- .../sdk/go/serviceability/deserialize.go | 11 +++ 5 files changed, 112 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b32a7d2f5f..689d1352ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,9 @@ All notable changes to this project will be documented in this file. - Go, Python, and TypeScript serviceability readers parse the trailing `new_interfaces` vec on `Device` with size-prefixed (u16 size + u8 version + body) forward-compat framing. Empty trailing falls back to rebuilding `new_interfaces` from the legacy enum vec, matching the Rust device reader. Length mismatch between the legacy and trailing vecs is surfaced as an error (Python/TS raise; Go sets `Device.DeserializeError`). Bumps `CURRENT_INTERFACE_VERSION` / `CurrentInterfaceVersion` to `4` across SDKs to match Rust's `CURRENT_INTERFACE_SCHEMA_VERSION` ([#3660](https://github.com/malbeclabs/doublezero/issues/3660)) - Regenerate `device.{bin,json}` through Device's custom serializer with a populated `new_interfaces` vec (one Vpnv4 loopback carrying a `FlexAlgoNodeSegment`, one physical user-tunnel-endpoint), and add `device_legacy.{bin,json}` (legacy `interfaces` vec only, no trailing bytes — exercises the SDK legacy-fallback path) and `device_future_version.{bin,json}` (last trailing-vec element doctored to `version=5` with 8 trailing junk bytes — exercises the SDK skip-to-end path). Adds fixture-driven Go SDK tests; extends the existing Python/TS fixture tests to cover all three Device fixtures ([#3661](https://github.com/malbeclabs/doublezero/issues/3661)) - Smartcontract - - Delete `InterfaceV3` and the `InterfaceDeprecated::V3` variant from the serviceability program. V3 was added by an earlier change, never written to production accounts, and reverted in #3653 / no longer produced after #3667; this removes the dead type. Discriminant 3 is now an unused reserved slot in `InterfaceDeprecated`'s encoding space — unknown discriminants fall through to `InterfaceV2::default()`. Removes the V3 struct, its helper impls (`From`, `TryFrom<&InterfaceV1>`, `Default`, `TryFrom<&InterfaceV3> for InterfaceV2`), V3 match arms in `InterfaceDeprecated::to_v2`/`size`/`Device::TryFrom`, and the V3 cross-language byte-layout debug test. On-disk write format is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) + - Delete `InterfaceV3` and the `InterfaceDeprecated::V3` variant from the serviceability program. The V3 type, its helper impls (`From`, `TryFrom<&InterfaceV1>`, `Default`, `TryFrom<&InterfaceV3> for InterfaceV2`), the V3 match arms in `InterfaceDeprecated::to_v2`/`size`/`Device::TryFrom`, and the V3 cross-language byte-layout debug test are removed. The custom `BorshDeserialize for InterfaceDeprecated` keeps a discriminant-3 read shim that consumes the V2 body + the trailing `flex_algo_node_segments` vec and surfaces the entry as `InterfaceDeprecated::V2` — required because mainnet-beta still has one device (`la2r-dz01`, interface `Ethernet20/1`) with a V3 entry in its legacy slot. Segments are dropped on this read path; they live in the trailing forward-compat `Device::interfaces` vec post-#3667. On-disk write format is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) - SDK - - Drop V3 handling from the Go, Python, and TypeScript serviceability readers: remove `DeserializeInterfaceV3` (Go) and the `version === 3` / `version == 3` legacy-slot branches (Python/TS); remove the `TestDeserializeInterfaceV3CrossLanguage` Go test. The forward-compat trailing `interfaces` vec continues to carry `flex_algo_node_segments` via the size-prefixed body — that path is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) + - Same legacy-V3 read shim in the Go, Python, and TypeScript serviceability readers: discriminant-3 entries in the legacy `deprecated_interfaces` slot are read as V2 (V2 body + consumed-and-dropped segments vec). The forward-compat trailing `interfaces` vec continues to carry `flex_algo_node_segments` via the size-prefixed body — that path is unchanged ([#3664](https://github.com/malbeclabs/doublezero/issues/3664)) ## [v0.21.0](https://github.com/malbeclabs/doublezero/compare/client/v0.20.0...client/v0.21.0) - 2026-05-01 diff --git a/sdk/serviceability/python/serviceability/state.py b/sdk/serviceability/python/serviceability/state.py index 9ddc39d6b7..84ef3056c5 100644 --- a/sdk/serviceability/python/serviceability/state.py +++ b/sdk/serviceability/python/serviceability/state.py @@ -445,8 +445,10 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.version = r.read_u8() if iface.version > CURRENT_INTERFACE_VERSION - 1: return iface - # Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was V3 (transient, - # never shipped) and is intentionally unhandled. + # Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was a transient V3 + # format (V2 body + flex_algo_node_segments vec); the type is gone but + # pre-existing on-chain accounts still contain V3 entries, so we consume + # the bytes and project to V2 (segments dropped). if iface.version == 0: iface.status = InterfaceStatus(r.read_u8()) iface.name = r.read_string() @@ -456,7 +458,7 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.ip_net = r.read_network_v4() iface.node_segment_idx = r.read_u16() iface.user_tunnel_endpoint = r.read_bool() - elif iface.version in (1, 2): + elif iface.version in (1, 2, 3): iface.status = InterfaceStatus(r.read_u8()) iface.name = r.read_string() iface.interface_type = InterfaceType(r.read_u8()) @@ -471,6 +473,11 @@ def from_reader(cls, r: DefensiveReader) -> Interface: iface.ip_net = r.read_network_v4() iface.node_segment_idx = r.read_u16() iface.user_tunnel_endpoint = r.read_bool() + if iface.version == 3: + seg_count = r.read_u32() + for _ in range(seg_count): + _read_pubkey(r) + r.read_u16() return iface @classmethod diff --git a/sdk/serviceability/typescript/serviceability/state.ts b/sdk/serviceability/typescript/serviceability/state.ts index bd74066aff..9c28910156 100644 --- a/sdk/serviceability/typescript/serviceability/state.ts +++ b/sdk/serviceability/typescript/serviceability/state.ts @@ -530,8 +530,10 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { return iface; } - // Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was V3 (transient, - // never shipped) and is intentionally unhandled. + // Discriminants: 0=V1, 1 or 2=V2. Discriminant 3 was a transient V3 format + // (V2 body + flex_algo_node_segments vec); the type is gone but pre-existing + // on-chain accounts still contain V3 entries, so we consume the bytes and + // project to V2 (segments dropped). if (iface.version === 0) { iface.status = r.readU8(); iface.name = r.readString(); @@ -541,7 +543,7 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { iface.ipNet = r.readNetworkV4(); iface.nodeSegmentIdx = r.readU16(); iface.userTunnelEndpoint = r.readBool(); - } else if (iface.version === 1 || iface.version === 2) { + } else if (iface.version === 1 || iface.version === 2 || iface.version === 3) { iface.status = r.readU8(); iface.name = r.readString(); iface.interfaceType = r.readU8(); @@ -556,6 +558,14 @@ function deserializeInterface(r: DefensiveReader): DeviceInterface { iface.ipNet = r.readNetworkV4(); iface.nodeSegmentIdx = r.readU16(); iface.userTunnelEndpoint = r.readBool(); + if (iface.version === 3) { + const segCount = r.readU32(); + for (let i = 0; i < segCount; i++) { + if (r.remaining < 34) break; + readPubkey(r); + r.readU16(); + } + } } return iface; diff --git a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs index c6138f8fd7..4951f6d739 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/interface.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/interface.rs @@ -402,8 +402,10 @@ pub enum InterfaceDeprecated { V1(InterfaceV1) = 0, /// Discriminant 1: V2 format. Does NOT include flex_algo_node_segments. /// Discriminant 2 is reserved (never written). Discriminant 3 was a transient - /// V3 format that never reached production and has been removed; the slot is - /// reserved and unused. + /// V3 format (V2 body + a `flex_algo_node_segments` vec) that no longer has + /// a corresponding type — the deserializer still consumes V3 bytes from + /// pre-existing on-chain accounts and projects them to V2 (segments dropped), + /// but nothing writes V3 going forward. V2(InterfaceV2) = 1, } @@ -417,6 +419,16 @@ impl borsh::BorshDeserialize for InterfaceDeprecated { 1 | 2 => Ok(InterfaceDeprecated::V2( borsh::BorshDeserialize::deserialize_reader(reader)?, )), + // Discriminant 3 (legacy V3): consume V2 body + the trailing + // flex_algo_node_segments vec, then surface as V2. The segments are + // dropped — they live in the trailing forward-compat `interfaces` vec + // on Device post-#3667. + 3 => { + let v2: InterfaceV2 = borsh::BorshDeserialize::deserialize_reader(reader)?; + let _segments: Vec = + borsh::BorshDeserialize::deserialize_reader(reader)?; + Ok(InterfaceDeprecated::V2(v2)) + } _ => Ok(InterfaceDeprecated::V2(InterfaceV2::default())), } } @@ -775,6 +787,68 @@ fn test_interface_version() { assert!(iface_v2.user_tunnel_endpoint); } +/// Hand-craft a `Vec` whose first element is a legacy V3 +/// byte stream (discriminant 3 + V2 body + flex_algo_node_segments vec) and a +/// trailing V2 element. Asserts that the V3 bytes are fully consumed (segments +/// included) so the V2 element after them deserializes correctly — guards +/// against the byte-misalignment that broke `sdk-compat-test` on mainnet-beta +/// device `la2r-dz01` (discriminant 3 entry `Ethernet20/1`). +#[test] +fn test_interface_deprecated_consumes_legacy_v3_bytes() { + use crate::state::topology::FlexAlgoNodeSegment; + use solana_program::pubkey::Pubkey; + + // Build a V2 body and append the V3-only flex_algo_node_segments vec by hand. + let v2 = InterfaceV2 { + status: InterfaceStatus::Activated, + name: "Ethernet20/1".to_string(), + interface_type: InterfaceType::Physical, + interface_cyoa: InterfaceCYOA::None, + interface_dia: InterfaceDIA::None, + loopback_type: LoopbackType::None, + bandwidth: 1_000_000_000, + cir: 0, + mtu: 9000, + routing_mode: RoutingMode::BGP, + vlan_id: 0, + ip_net: "10.0.0.1/30".parse().unwrap(), + node_segment_idx: 0, + user_tunnel_endpoint: false, + }; + let segments = vec![FlexAlgoNodeSegment { + topology: Pubkey::new_unique(), + node_segment_idx: 7, + }]; + let trailing = InterfaceV2 { + name: "Ethernet1".to_string(), + ..InterfaceV2::default() + }; + + // Wire bytes: u32 vec_len=2, then [disc=3, V2 body, segments vec] + [disc=1, V2 body]. + let mut bytes = Vec::new(); + bytes.extend_from_slice(&2u32.to_le_bytes()); + bytes.push(3); // legacy V3 discriminant + bytes.extend_from_slice(&borsh::to_vec(&v2).unwrap()); + bytes.extend_from_slice(&borsh::to_vec(&segments).unwrap()); + bytes.push(1); // V2 discriminant + bytes.extend_from_slice(&borsh::to_vec(&trailing).unwrap()); + + let decoded: Vec = borsh::BorshDeserialize::try_from_slice(&bytes) + .expect("legacy V3 bytes should be consumed without misalignment"); + assert_eq!(decoded.len(), 2); + match &decoded[0] { + InterfaceDeprecated::V2(d) => { + assert_eq!(d.name, "Ethernet20/1"); + assert_eq!(d.mtu, 9000); + } + other => panic!("expected V3 to project to V2, got {:?}", other), + } + match &decoded[1] { + InterfaceDeprecated::V2(d) => assert_eq!(d.name, "Ethernet1"), + other => panic!("expected trailing V2, got {:?}", other), + } +} + #[cfg(test)] mod test_interface_validate { use super::*; diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index f1f5b7dab5..ddc0653d59 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -90,6 +90,10 @@ func DeserializeContributor(reader *ByteReader, contributor *Contributor) { // 0 — V1: original format (no CYOA/DIA/Bandwidth fields) // 1 — V2: adds CYOA, DIA, Bandwidth, Cir, Mtu, RoutingMode // 2 — reserved, never written +// 3 — V3 (legacy): V2 body + a flex_algo_node_segments vec. No longer +// written, but pre-existing on-chain accounts still contain V3 entries +// in the legacy slot. We consume the bytes and surface as V2 — segments +// live in the trailing forward-compat interfaces vec on Device. // // The on-chain Device serializer projects the legacy deprecated_interfaces slot as V2 // (per #3653). flex_algo_node_segments lives only in the trailing forward-compat @@ -102,6 +106,13 @@ func DeserializeInterface(reader *ByteReader, iface *Interface) { DeserializeInterfaceV1(reader, iface) case 1, 2: // V2 DeserializeInterfaceV2(reader, iface) + case 3: // legacy V3 — consume V2 body + drop segments + DeserializeInterfaceV2(reader, iface) + segCount := reader.ReadU32() + for i := uint32(0); i < segCount; i++ { + _ = reader.ReadPubkey() + _ = reader.ReadU16() + } default: log.Println("DeserializeInterface: Unsupported interface version", iface.Version) }