From 6fe20a68ed22e791896120ef89f1149f104bdbe0 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Thu, 18 Dec 2025 20:07:18 +0000 Subject: [PATCH 1/4] impr: Return 500 with information on message processing error --- src/hb_ao.erl | 30 +++++++++++++++++++++++++++++- src/hb_store.erl | 17 ++--------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/hb_ao.erl b/src/hb_ao.erl index 69c7a000c..1c0a49dc4 100644 --- a/src/hb_ao.erl +++ b/src/hb_ao.erl @@ -166,7 +166,35 @@ resolve_many([ID], Opts) when ?IS_ID(ID) -> % to use in looking up a cached result. ?event(ao_core, {stage, na, resolve_directly_to_id, ID, {opts, Opts}}, Opts), try {ok, ensure_message_loaded(ID, Opts)} - catch _:_:_ -> {error, not_found} + catch + _:{necessary_message_not_found, _, _}:_StackTrace -> + {error, not_found}; + Class:Reason:Stacktrace -> + ?event(error, + {store_call_failed_retrying, + {class, Class}, + {reason, Reason}, + {stacktrace, {trace, Stacktrace}} + } + ), + FailMode = hb_opts:get(debug_print_fail_mode, quiet, Opts), + Body = case FailMode of + quiet -> + #{ + <<"type">> => Class, + <<"details">> => <<"Cannot process the message">> + }; + long -> + ClassBin = iolist_to_binary(io_lib:format("~p", [Class])), + ReasonBin = iolist_to_binary(io_lib:format("~p", [Reason])), + StacktraceBin = iolist_to_binary(io_lib:format("~p", [Stacktrace])), + #{ + <<"type">> => ClassBin, + <<"details">> => ReasonBin, + <<"stacktrace">> => StacktraceBin + } + end, + {failure, Body#{<<"status">> => 500}} end; resolve_many(ListMsg, Opts) when is_map(ListMsg) -> % We have been given a message rather than a list of messages, so we should diff --git a/src/hb_store.erl b/src/hb_store.erl index 5cb9e5527..c804e13f7 100644 --- a/src/hb_store.erl +++ b/src/hb_store.erl @@ -362,12 +362,11 @@ do_call_function([Store = #{<<"access">> := Access} | Rest], Function, Args) -> end; do_call_function([Store = #{<<"store-module">> := Mod} | Rest], Function, Args) -> % Attempt to apply the function. If it fails, try the next store. - try apply_store_function(Mod, Store, Function, Args) of + case apply_store_function(Mod, Store, Function, Args) of not_found -> do_call_function(Rest, Function, Args); Result -> Result - catch _:_:_ -> do_call_function(Rest, Function, Args) end. %% @doc Apply a store function, checking if the store returns a retry request or @@ -380,21 +379,9 @@ apply_store_function(_Mod, _Store, _Function, _Args, 0) -> % Too many attempts have already failed. Bail. not_found; apply_store_function(Mod, Store, Function, Args, AttemptsRemaining) -> - try apply(Mod, Function, [Store | Args]) of + case apply(Mod, Function, [Store | Args]) of retry -> retry(Mod, Store, Function, Args, AttemptsRemaining); Other -> Other - catch Class:Reason:Stacktrace -> - ?event(store_error, - {store_call_failed_retrying, - {store, Store}, - {function, Function}, - {args, Args}, - {class, Class}, - {reason, Reason}, - {stacktrace, {trace, Stacktrace}} - } - ), - retry(Mod, Store, Function, Args, AttemptsRemaining) end. %% @doc Stop and start the store, then retry. From bb90d1896b95c4587579d02bbcb1e4446edcb5db Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Thu, 18 Dec 2025 15:23:20 -0500 Subject: [PATCH 2/4] wip: surface definitive `failure` responses from ID reads on stores --- src/hb_store.erl | 18 ++++++++++++++++-- src/hb_store_gateway.erl | 14 ++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/hb_store.erl b/src/hb_store.erl index c804e13f7..f6048681d 100644 --- a/src/hb_store.erl +++ b/src/hb_store.erl @@ -362,11 +362,12 @@ do_call_function([Store = #{<<"access">> := Access} | Rest], Function, Args) -> end; do_call_function([Store = #{<<"store-module">> := Mod} | Rest], Function, Args) -> % Attempt to apply the function. If it fails, try the next store. - case apply_store_function(Mod, Store, Function, Args) of + try apply_store_function(Mod, Store, Function, Args) of not_found -> do_call_function(Rest, Function, Args); Result -> Result + catch _:_:_ -> do_call_function(Rest, Function, Args) end. %% @doc Apply a store function, checking if the store returns a retry request or @@ -379,9 +380,22 @@ apply_store_function(_Mod, _Store, _Function, _Args, 0) -> % Too many attempts have already failed. Bail. not_found; apply_store_function(Mod, Store, Function, Args, AttemptsRemaining) -> - case apply(Mod, Function, [Store | Args]) of + try apply(Mod, Function, [Store | Args]) of + failure -> failure; retry -> retry(Mod, Store, Function, Args, AttemptsRemaining); Other -> Other + catch Class:Reason:Stacktrace -> + ?event(store_error, + {store_call_failed_retrying, + {store, Store}, + {function, Function}, + {args, Args}, + {class, Class}, + {reason, Reason}, + {stacktrace, {trace, Stacktrace}} + } + ), + retry(Mod, Store, Function, Args, AttemptsRemaining) end. %% @doc Stop and start the store, then retry. diff --git a/src/hb_store_gateway.erl b/src/hb_store_gateway.erl index 47e3c98bf..32ede8bc1 100644 --- a/src/hb_store_gateway.erl +++ b/src/hb_store_gateway.erl @@ -60,14 +60,24 @@ read(BaseStoreOpts, Key) -> case hb_store_remote_node:read_local_cache(StoreOpts, ID) of not_found -> ?event({gateway_read, {opts, StoreOpts}, {id, ID}, {subpath, Rest}}), - case hb_gateway_client:read(ID, StoreOpts) of - {error, _} -> + try hb_gateway_client:read(ID, StoreOpts) of + {error, no} -> ?event({read_not_found, {key, ID}}), not_found; {ok, Message} -> ?event({read_found, {key, ID}}), hb_store_remote_node:maybe_cache(StoreOpts, Message), extract_path_value(Message, Rest, StoreOpts) + catch Class:Reason:Stacktrace -> + ?event( + gateway, + {read_failed, + {class, Class}, + {reason, Reason}, + {stacktrace, {trace, Stacktrace}} + } + ), + failure end; {ok, CachedMessage} -> extract_path_value(CachedMessage, Rest, StoreOpts) From a42a54acd5a936b0ee851c7b49a7c04db09ef1f4 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Fri, 19 Dec 2025 17:22:24 +0000 Subject: [PATCH 3/4] feat: Support failure fallback --- src/hb_ao.erl | 32 +++----------------------------- src/hb_cache.erl | 4 +++- src/hb_store.erl | 1 - src/hb_store_gateway.erl | 20 +++++++++++++++++++- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/hb_ao.erl b/src/hb_ao.erl index 1c0a49dc4..af4355c89 100644 --- a/src/hb_ao.erl +++ b/src/hb_ao.erl @@ -166,35 +166,7 @@ resolve_many([ID], Opts) when ?IS_ID(ID) -> % to use in looking up a cached result. ?event(ao_core, {stage, na, resolve_directly_to_id, ID, {opts, Opts}}, Opts), try {ok, ensure_message_loaded(ID, Opts)} - catch - _:{necessary_message_not_found, _, _}:_StackTrace -> - {error, not_found}; - Class:Reason:Stacktrace -> - ?event(error, - {store_call_failed_retrying, - {class, Class}, - {reason, Reason}, - {stacktrace, {trace, Stacktrace}} - } - ), - FailMode = hb_opts:get(debug_print_fail_mode, quiet, Opts), - Body = case FailMode of - quiet -> - #{ - <<"type">> => Class, - <<"details">> => <<"Cannot process the message">> - }; - long -> - ClassBin = iolist_to_binary(io_lib:format("~p", [Class])), - ReasonBin = iolist_to_binary(io_lib:format("~p", [Reason])), - StacktraceBin = iolist_to_binary(io_lib:format("~p", [Stacktrace])), - #{ - <<"type">> => ClassBin, - <<"details">> => ReasonBin, - <<"stacktrace">> => StacktraceBin - } - end, - {failure, Body#{<<"status">> => 500}} + catch _:_:_ -> {error, not_found} end; resolve_many(ListMsg, Opts) when is_map(ListMsg) -> % We have been given a message rather than a list of messages, so we should @@ -854,6 +826,8 @@ ensure_message_loaded(MsgID, Opts) when ?IS_ID(MsgID) -> case hb_cache:read(MsgID, Opts) of {ok, LoadedMsg} -> LoadedMsg; + failure -> + failure; not_found -> throw({necessary_message_not_found, <<"/">>, MsgID}) end; diff --git a/src/hb_cache.erl b/src/hb_cache.erl index 6b945f6bc..98c9f0a63 100644 --- a/src/hb_cache.erl +++ b/src/hb_cache.erl @@ -468,12 +468,14 @@ store_read(Target, Path, Store, Opts) -> {store, Store} }), case hb_store:type(Store, ResolvedFullPath) of + failure -> failure; not_found -> not_found; simple -> ?event({reading_data, ResolvedFullPath}), case hb_store:read(Store, ResolvedFullPath) of {ok, Bin} -> {ok, Bin}; - not_found -> not_found + not_found -> not_found; + failure -> failure end; composite -> ?event({reading_composite, ResolvedFullPath}), diff --git a/src/hb_store.erl b/src/hb_store.erl index f6048681d..5cb9e5527 100644 --- a/src/hb_store.erl +++ b/src/hb_store.erl @@ -381,7 +381,6 @@ apply_store_function(_Mod, _Store, _Function, _Args, 0) -> not_found; apply_store_function(Mod, Store, Function, Args, AttemptsRemaining) -> try apply(Mod, Function, [Store | Args]) of - failure -> failure; retry -> retry(Mod, Store, Function, Args, AttemptsRemaining); Other -> Other catch Class:Reason:Stacktrace -> diff --git a/src/hb_store_gateway.erl b/src/hb_store_gateway.erl index 32ede8bc1..cb7a5e6ec 100644 --- a/src/hb_store_gateway.erl +++ b/src/hb_store_gateway.erl @@ -13,6 +13,7 @@ list(StoreOpts, Key) -> ?event(store_gateway, executing_list), case read(StoreOpts, Key) of not_found -> not_found; + failure -> failure; {ok, Message} -> {ok, hb_maps:keys(Message, StoreOpts)} end. @@ -23,6 +24,7 @@ type(StoreOpts, Key) -> ?event(store_gateway, executing_type), case read(StoreOpts, Key) of not_found -> not_found; + failure -> failure; {ok, Data} -> ?event({type, hb_private:reset(hb_message:uncommitted(Data, StoreOpts))}), IsFlat = lists:all( @@ -61,7 +63,7 @@ read(BaseStoreOpts, Key) -> not_found -> ?event({gateway_read, {opts, StoreOpts}, {id, ID}, {subpath, Rest}}), try hb_gateway_client:read(ID, StoreOpts) of - {error, no} -> + {error, _} -> ?event({read_not_found, {key, ID}}), not_found; {ok, Message} -> @@ -446,6 +448,22 @@ verifiability_test() -> ?event({verifying, {structured, Structured}, {original, Message}}), ?assert(hb_message:verify(Structured)). +%% @doc Reading an unsupported transaction should fail +failure_to_process_message_test() -> + hb_http_server:start_node(#{}), + failure = + hb_cache:read( + <<"j0_mJMXG2YO4oRcOtjYsNoUJbN2TaKLo4nTtbhKqnEU">>, + #{ + store => + [ + #{ + <<"store-module">> => hb_store_gateway + } + ] + } + ). + %% @doc Test that another HyperBEAM node offering the `~query@1.0' device can %% be used as a store. remote_hyperbeam_node_ans104_test() -> From c27f2fa1d1dba65c4b95adca672b8ffee9c56869 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Fri, 19 Dec 2025 17:27:37 +0000 Subject: [PATCH 4/4] impr: hb_store_gateway failure test --- src/hb_store_gateway.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hb_store_gateway.erl b/src/hb_store_gateway.erl index cb7a5e6ec..cef532aa1 100644 --- a/src/hb_store_gateway.erl +++ b/src/hb_store_gateway.erl @@ -448,10 +448,10 @@ verifiability_test() -> ?event({verifying, {structured, Structured}, {original, Message}}), ?assert(hb_message:verify(Structured)). -%% @doc Reading an unsupported transaction should fail +%% @doc Reading an unsupported signature type transaction should fail failure_to_process_message_test() -> hb_http_server:start_node(#{}), - failure = + ?assertEqual(failure, hb_cache:read( <<"j0_mJMXG2YO4oRcOtjYsNoUJbN2TaKLo4nTtbhKqnEU">>, #{ @@ -462,7 +462,8 @@ failure_to_process_message_test() -> } ] } - ). + ) + ). %% @doc Test that another HyperBEAM node offering the `~query@1.0' device can %% be used as a store.