@@ -968,81 +968,85 @@ impl Operation for GetOp {
968968
969969 // Put contract locally if needed
970970 if should_put {
971- tracing:: debug!( tx = %id, %key, %is_original_requester, %subscribe_requested, "Putting contract at executor" ) ;
972- let res = op_manager
973- . notify_contract_handler ( ContractHandlerEvent :: PutQuery {
971+ // First check if the local state matches the incoming state
972+ // to avoid triggering validation errors in contracts that implement
973+ // idempotency checks in their update_state() method (issue #2018)
974+ let local_state = op_manager
975+ . notify_contract_handler ( ContractHandlerEvent :: GetQuery {
974976 key,
975- state : value. clone ( ) ,
976- related_contracts : RelatedContracts :: default ( ) , // fixme: i think we need to get the related contracts so the final put is ok
977- contract : contract. clone ( ) ,
977+ return_contract_code : false ,
978978 } )
979- . await ?;
980-
981- match res {
982- ContractHandlerEvent :: PutResponse { new_value : Ok ( _) } => {
983- tracing:: debug!( tx = %id, %key, "Contract put at executor" ) ;
984- let is_subscribed_contract =
985- op_manager. ring . is_seeding_contract ( & key) ;
986-
987- // Start subscription if not already seeding
988- if !is_subscribed_contract {
989- tracing:: debug!( tx = %id, %key, peer = %op_manager. ring. connection_manager. get_peer_key( ) . unwrap( ) , "Contract not cached @ peer, caching" ) ;
990- op_manager. ring . seed_contract ( key) ;
991- let mut new_skip_list = skip_list. clone ( ) ;
992- new_skip_list. insert ( sender. peer . clone ( ) ) ;
993-
994- super :: start_subscription_request ( op_manager, key) . await ;
995- }
979+ . await ;
980+
981+ let state_matches = match local_state {
982+ Ok ( ContractHandlerEvent :: GetResponse {
983+ response :
984+ Ok ( StoreResponse {
985+ state : Some ( local) , ..
986+ } ) ,
987+ ..
988+ } ) => {
989+ // Compare the actual state bytes
990+ local. as_ref ( ) == value. as_ref ( )
996991 }
997- ContractHandlerEvent :: PutResponse {
998- new_value : Err ( err) ,
999- } => {
1000- if is_original_requester {
1001- // Original requester, return error
1002- tracing:: debug!( tx = %id, error = %err, "Failed put at executor" ) ;
1003- return Err ( OpError :: ExecutorError ( err) ) ;
1004- } else {
1005- // Forward error to requester
1006- let mut new_skip_list = skip_list. clone ( ) ;
1007- new_skip_list. insert ( sender. peer . clone ( ) ) ;
992+ _ => false , // No local state or error - we should try to cache
993+ } ;
1008994
1009- let requester = requester. unwrap ( ) ;
995+ if state_matches {
996+ tracing:: debug!(
997+ tx = %id,
998+ %key,
999+ "Local state matches network state, skipping redundant cache"
1000+ ) ;
1001+ // State already cached and identical, mark as seeded if needed
1002+ if !op_manager. ring . is_seeding_contract ( & key) {
1003+ tracing:: debug!( tx = %id, %key, "Marking contract as seeded" ) ;
1004+ op_manager. ring . seed_contract ( key) ;
1005+ super :: start_subscription_request ( op_manager, key) . await ;
1006+ }
1007+ } else {
1008+ tracing:: debug!( tx = %id, %key, %is_original_requester, %subscribe_requested, "Putting contract at executor - state differs from local cache" ) ;
1009+ let res = op_manager
1010+ . notify_contract_handler ( ContractHandlerEvent :: PutQuery {
1011+ key,
1012+ state : value. clone ( ) ,
1013+ related_contracts : RelatedContracts :: default ( ) , // fixme: i think we need to get the related contracts so the final put is ok
1014+ contract : contract. clone ( ) ,
1015+ } )
1016+ . await ?;
10101017
1018+ match res {
1019+ ContractHandlerEvent :: PutResponse { new_value : Ok ( _) } => {
1020+ tracing:: debug!( tx = %id, %key, "Contract put at executor" ) ;
1021+ let is_subscribed_contract =
1022+ op_manager. ring . is_seeding_contract ( & key) ;
1023+
1024+ // Start subscription if not already seeding
1025+ if !is_subscribed_contract {
1026+ tracing:: debug!( tx = %id, %key, peer = %op_manager. ring. connection_manager. get_peer_key( ) . unwrap( ) , "Contract not cached @ peer, caching" ) ;
1027+ op_manager. ring . seed_contract ( key) ;
1028+ super :: start_subscription_request ( op_manager, key) . await ;
1029+ }
1030+ }
1031+ ContractHandlerEvent :: PutResponse {
1032+ new_value : Err ( err) ,
1033+ } => {
1034+ // Local caching failed, but GET operation succeeded
1035+ // Log warning and continue - caching is an optimization, not required
10111036 tracing:: warn!(
10121037 tx = %id,
10131038 %key,
1014- %sender . peer ,
1015- target = %requester ,
1016- "Failed put at executor, returning response to requester" ,
1039+ error = %err ,
1040+ %is_original_requester ,
1041+ "Failed to cache contract locally during GET - continuing with operation"
10171042 ) ;
1018-
1019- op_manager
1020- . notify_op_change (
1021- NetMessage :: from ( GetMsg :: ReturnGet {
1022- id,
1023- key,
1024- value : StoreResponse {
1025- state : None ,
1026- contract : None ,
1027- } ,
1028- sender : sender. clone ( ) ,
1029- target : requester. clone ( ) ,
1030- skip_list : new_skip_list,
1031- } ) ,
1032- OpEnum :: Get ( GetOp {
1033- id,
1034- state : self . state ,
1035- result : None ,
1036- stats,
1037- } ) ,
1038- )
1039- . await ?;
1040- return Err ( OpError :: StatePushed ) ;
1043+ // Don't return error - the GET succeeded, caching is optional
1044+ // Continue to process the GET result below
10411045 }
1046+ _ => unreachable ! (
1047+ "PutQuery from Get operation should always return PutResponse"
1048+ ) ,
10421049 }
1043- _ => unreachable ! (
1044- "PutQuery from Get operation should always return PutResponse"
1045- ) ,
10461050 }
10471051 }
10481052
0 commit comments