@@ -382,11 +382,15 @@ impl Proxy {
382382 ///
383383 /// `port_attrs = None` means the CVM didn't report port attributes (legacy
384384 /// CVM). The gateway will lazily fetch them via Info() on first connection.
385+ ///
386+ /// `compose_hash` is the attested compose_hash — used to invalidate any
387+ /// cached `port_attrs` when the app is upgraded.
385388 pub fn do_register_cvm (
386389 & self ,
387390 app_id : & str ,
388391 instance_id : & str ,
389392 client_public_key : & str ,
393+ compose_hash : & str ,
390394 port_attrs : Option < BTreeMap < u16 , PortFlags > > ,
391395 ) -> Result < RegisterCvmResponse > {
392396 let mut state = self . lock ( ) ;
@@ -407,7 +411,13 @@ impl Proxy {
407411 bail ! ( "[{instance_id}] client public key is empty" ) ;
408412 }
409413 let client_info = state
410- . new_client_by_id ( instance_id, app_id, client_public_key, port_attrs)
414+ . new_client_by_id (
415+ instance_id,
416+ app_id,
417+ client_public_key,
418+ compose_hash,
419+ port_attrs,
420+ )
411421 . context ( "failed to allocate IP address for client" ) ?;
412422 if let Err ( err) = state. reconfigure ( ) {
413423 error ! ( "failed to reconfigure: {err:?}" ) ;
@@ -454,6 +464,7 @@ fn build_state_from_kv_store(instances: BTreeMap<String, InstanceData>) -> Proxy
454464 . checked_add ( Duration :: from_secs ( data. reg_time ) )
455465 . unwrap_or ( UNIX_EPOCH ) ,
456466 port_attrs : data. port_attrs ,
467+ port_attrs_hash : data. port_attrs_hash ,
457468 connections : Default :: default ( ) ,
458469 } ;
459470 state. allocated_addresses . insert ( data. ip ) ;
@@ -748,6 +759,7 @@ fn reload_instances_from_kv_store(proxy: &Proxy, store: &KvStore) -> Result<()>
748759 . checked_add ( Duration :: from_secs ( data. reg_time ) )
749760 . unwrap_or ( UNIX_EPOCH ) ,
750761 port_attrs : data. port_attrs . clone ( ) ,
762+ port_attrs_hash : data. port_attrs_hash . clone ( ) ,
751763 connections : Default :: default ( ) ,
752764 } ;
753765
@@ -829,6 +841,7 @@ impl ProxyState {
829841 id : & str ,
830842 app_id : & str ,
831843 public_key : & str ,
844+ compose_hash : & str ,
832845 port_attrs : Option < BTreeMap < u16 , PortFlags > > ,
833846 ) -> Result < InstanceInfo > {
834847 if id. is_empty ( ) {
@@ -848,11 +861,20 @@ impl ProxyState {
848861 // Update reg_time so other nodes will pick up the change
849862 existing. reg_time = SystemTime :: now ( ) ;
850863 }
864+ // App upgrade detection: a different attested compose_hash invalidates
865+ // any cached port_attrs from the previous code.
866+ if existing. port_attrs_hash != compose_hash {
867+ info ! (
868+ "compose_hash changed for instance {id} ({} -> {compose_hash}), \
869+ invalidating cached port_attrs",
870+ existing. port_attrs_hash
871+ ) ;
872+ existing. port_attrs = None ;
873+ existing. port_attrs_hash = compose_hash. to_string ( ) ;
874+ }
851875 // Only override cached port_attrs when the caller actually reports
852- // them. A `None` request (legacy CVM) means "I don't know" — don't
853- // wipe attrs learned at an earlier registration or lazy fetch. Same
854- // instance_id implies same compose_hash, so cached attrs can't be
855- // stale relative to the real app-compose.
876+ // them. A `None` request (legacy CVM) means "I don't know" — let
877+ // the lazy fetch path run again.
856878 if port_attrs. is_some ( ) {
857879 existing. port_attrs = port_attrs. clone ( ) ;
858880 }
@@ -865,6 +887,7 @@ impl ProxyState {
865887 public_key : existing. public_key . clone ( ) ,
866888 reg_time : encode_ts ( existing. reg_time ) ,
867889 port_attrs : existing. port_attrs . clone ( ) ,
890+ port_attrs_hash : existing. port_attrs_hash . clone ( ) ,
868891 } ;
869892 if let Err ( err) = self . kv_store . sync_instance ( & existing. id , & data) {
870893 error ! ( "failed to sync existing instance to KvStore: {err:?}" ) ;
@@ -884,6 +907,7 @@ impl ProxyState {
884907 public_key : public_key. to_string ( ) ,
885908 reg_time : SystemTime :: now ( ) ,
886909 port_attrs,
910+ port_attrs_hash : compose_hash. to_string ( ) ,
887911 connections : Default :: default ( ) ,
888912 } ;
889913 self . add_instance ( host_info. clone ( ) ) ;
@@ -921,6 +945,7 @@ impl ProxyState {
921945 public_key : info. public_key . clone ( ) ,
922946 reg_time : encode_ts ( info. reg_time ) ,
923947 port_attrs : Some ( attrs) ,
948+ port_attrs_hash : info. port_attrs_hash . clone ( ) ,
924949 } ;
925950 if let Err ( err) = self . kv_store . sync_instance ( instance_id, & data) {
926951 error ! ( "failed to sync updated port_attrs to KvStore: {err:?}" ) ;
@@ -935,6 +960,7 @@ impl ProxyState {
935960 public_key : info. public_key . clone ( ) ,
936961 reg_time : encode_ts ( info. reg_time ) ,
937962 port_attrs : info. port_attrs . clone ( ) ,
963+ port_attrs_hash : info. port_attrs_hash . clone ( ) ,
938964 } ;
939965 if let Err ( err) = self . kv_store . sync_instance ( & info. id , & data) {
940966 error ! ( "failed to sync instance to KvStore: {err:?}" ) ;
@@ -1339,14 +1365,20 @@ impl GatewayRpc for RpcHandler {
13391365 . context ( "App authorization failed" ) ?;
13401366 let app_id = hex:: encode ( & app_info. app_id ) ;
13411367 let instance_id = hex:: encode ( & app_info. instance_id ) ;
1368+ let compose_hash = hex:: encode ( & app_info. compose_hash ) ;
13421369 let port_attrs = request. port_attrs . map ( |list| {
13431370 list. attrs
13441371 . into_iter ( )
13451372 . map ( |p| ( p. port as u16 , PortFlags { pp : p. pp } ) )
13461373 . collect :: < BTreeMap < u16 , PortFlags > > ( )
13471374 } ) ;
1348- self . state
1349- . do_register_cvm ( & app_id, & instance_id, & request. client_public_key , port_attrs)
1375+ self . state . do_register_cvm (
1376+ & app_id,
1377+ & instance_id,
1378+ & request. client_public_key ,
1379+ & compose_hash,
1380+ port_attrs,
1381+ )
13501382 }
13511383
13521384 async fn acme_info ( self ) -> Result < AcmeInfoResponse > {
0 commit comments