Part of the forward-compatible Device interfaces refactor.
Design: https://gist.github.com/elitegreg/e1d27c97034656a980fa8d7628ae16c5
Depends on #3657 (NewInterface struct).
Scope
Append new_interfaces: Vec<NewInterface> to Device after max_multicast_publishers, behind a custom BorshSerialize that keeps the existing interfaces: Vec<Interface> (legacy enum vec) length-locked with the new vec via per-write projection.
The legacy field stays at its current byte position so old readers don't change.
Tasks
state/device.rs
Tests
Notes
- No processor changes in this issue. Keeping both vecs in sync is the responsibility of the device serializer, so write-path callers in subsequent issues can mutate
new_interfaces and rely on the projection.
Part of the forward-compatible Device interfaces refactor.
Design: https://gist.github.com/elitegreg/e1d27c97034656a980fa8d7628ae16c5
Depends on #3657 (NewInterface struct).
Scope
Append
new_interfaces: Vec<NewInterface>toDeviceaftermax_multicast_publishers, behind a customBorshSerializethat keeps the existinginterfaces: Vec<Interface>(legacy enum vec) length-locked with the new vec via per-write projection.The legacy field stays at its current byte position so old readers don't change.
Tasks
state/device.rs#[derive(BorshSerialize)]onDevice.pub new_interfaces: Vec<NewInterface>as the last field.BorshSerializeforDevice:interfacesfromnew_interfacesviaInterfaceV2::from(&NewInterface)wrapped asInterface::V2(...)before serializing (always V2 to match the post-smartcontract: revert default Interface to V2; keep V3 for migrate/backfill #3653 on-disk current).new_interfaceslands at the end.assert_eq!(legacy.len(), new_interfaces.len()).TryFrom<&[u8]> for Device(lines 481-523):interfacesat the existing offset.max_multicast_publishers.Vec<NewInterface>withunwrap_or_default()so legacy accounts (no trailing bytes) yieldVec::new().new_interfacesfrom legacy via the per-variantTryFrom<&InterfaceV1/V2> for NewInterfaceconversions. Otherwise use the trailing vec as-is.len(trailing) != len(legacy), panic — should not happen outside the empty-trailing case.Defaultimpl to initialize both vecs asVec::new().Device::find_interfacereturns(usize, &NewInterface)(was(usize, CurrentInterfaceVersion)). Note: ~30 call sites ripple — those move in subsequent issues, not this one. Where blockers arise, add a temporary helper that returns the legacy projection so this PR stays self-contained.Tests
test_device_serialize_keeps_vecs_in_sync: round-trip aDevicewith N entries; assertlen(legacy) == len(new_interfaces) == Nand per-element name equality.test_device_legacy_account_rebuilds_new_vec: hand-serialized device omitting the trailing vec —Device::try_frompopulatesnew_interfacesfrom legacy.test_device_skips_future_interface_in_new_vec: forgeversion=5on the firstnew_interfaceselement with N trailing junk bytes; both interfaces remain accessible.test_state_compatibility_devicecontinues to pass and now exercises the legacy fallback.Notes
new_interfacesand rely on the projection.