Skip to content

Commit 7929dfe

Browse files
sanityclaude
andauthored
fix: make get operation resilient to local caching failures (#2020)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f884c89 commit 7929dfe

File tree

1 file changed

+68
-64
lines changed
  • crates/core/src/operations

1 file changed

+68
-64
lines changed

crates/core/src/operations/get.rs

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)