diff --git a/src/dev_arweave.erl b/src/dev_arweave.erl index 377b5f2a1..09443a1c7 100644 --- a/src/dev_arweave.erl +++ b/src/dev_arweave.erl @@ -158,7 +158,7 @@ head_raw(Base, Request, Opts) -> ?event(debug_raw, {raw, {base, Base}, {request, Request}}), case find_key(<<"raw">>, Base, Request, Opts) of not_found -> {error, not_found}; - TXID -> + TXID when ?IS_ID(TXID) -> % Read the data from the local cache. IndexStore = hb_store_arweave:store_from_opts(Opts), case hb_store_arweave:read_offset(IndexStore, TXID) of @@ -182,7 +182,9 @@ head_raw(Base, Request, Opts) -> Opts ), {error, not_found} - end + end; + _ -> + {error, not_found} end. %% @doc Arweave transaction headers are not part of the Arweave data tree, and @@ -827,28 +829,43 @@ pending(Base, Request, Opts) -> % Retreive a bare TX header by its TXID request(<<"GET">>, <<"/unconfirmed_tx/", TXID/binary>>, Opts); {ok, RawOffset} -> - Offset = hb_util:int(RawOffset), - % Download an unconfirmed chunk by its offset - request( - <<"GET">>, - << - "/unconfirmed_chunk/", - TXID/binary, - "/", - (hb_util:bin(Offset))/binary - >>, - Opts#{ - exclude_data => - hb_util:bool( - find_key( - <<"exclude-data">>, - Base, - Request, - Opts - ) - ) - } - ) + try hb_util:int(RawOffset) of + Offset when Offset < 0 -> + {error, #{ + <<"status">> => 400, + <<"content-type">> => <<"application/json">>, + <<"body">> => <<"{\"error\":\"invalid_offset\"}">> + }}; + Offset -> + % Download an unconfirmed chunk by its offset + request( + <<"GET">>, + << + "/unconfirmed_chunk/", + TXID/binary, + "/", + (hb_util:bin(Offset))/binary + >>, + Opts#{ + exclude_data => + hb_util:bool( + find_key( + <<"exclude-data">>, + Base, + Request, + Opts + ) + ) + } + ) + catch + _:_ -> + {error, #{ + <<"status">> => 400, + <<"content-type">> => <<"application/json">>, + <<"body">> => <<"{\"error\":\"invalid_offset\"}">> + }} + end end end. @@ -1008,37 +1025,39 @@ to_tx_message(Type, ID, Path, {ok, #{ <<"body">> := Body }}, LogExtra, Opts) -> {tx, TXHeader} } ), - {ok, Data} = + DataRes = case hb_opts:get(exclude_data, false, Opts) of - true -> {ok, ?DEFAULT_DATA}; + true -> + {ok, ?DEFAULT_DATA}; false -> - DataRes = - case Type of - tx -> - request(<<"GET">>, <<"/raw/", ID/binary>>, Opts); - pending -> - get_chunk_range_relative( - 0, - TXHeader#tx.data_size, - ID, - Opts - ) - end, - case DataRes of - {ok, RawData} -> {ok, RawData}; - {error, not_found} -> {ok, ?DEFAULT_DATA}; - Error -> Error + case Type of + tx -> + request(<<"GET">>, <<"/raw/", ID/binary>>, Opts); + pending -> + get_chunk_range_relative( + 0, + TXHeader#tx.data_size, + ID, + Opts + ) end end, - { - ok, - hb_message:convert( - TXHeader#tx{ data = Data }, - <<"structured@1.0">>, - <<"tx@1.0">>, - Opts - ) - }. + case DataRes of + {ok, RawData} -> + { + ok, + hb_message:convert( + TXHeader#tx{ data = RawData }, + <<"structured@1.0">>, + <<"tx@1.0">>, + Opts + ) + }; + {error, not_found} -> + {ok, hb_message:convert(TXHeader, <<"structured@1.0">>, <<"tx@1.0">>, Opts)}; + Error -> + Error + end. event_request(Path, Method, Status, Extra) -> BaseList = [{request, {explicit, Path}}, {method, Method}, {status, Status}], @@ -1214,6 +1233,52 @@ best_response_non_map_error_round_trips_test() -> to_message(<<"/tx">>, <<"GET">>, {error, FailedConnect}, [], #{}) ). +tx_raw_fetch_error_round_trips_test() -> + {ok, MockNode, MockHandle} = hb_mock_server:start([ + {"/raw/:id", tx_raw, {500, <<"boom">>}} + ]), + ClientOpts = post_tx_json_client_opts(), + HeaderBody = post_tx_json_payload(ClientOpts), + TXID = maps:get(<<"id">>, hb_json:decode(HeaderBody)), + Opts = + ClientOpts#{ + routes => [ + #{ + <<"template">> => + #{ + <<"path">> => <<"^/arweave/raw">>, + <<"method">> => <<"GET">> + }, + <<"nodes">> => + [ + #{ + <<"match">> => <<"^/arweave">>, + <<"with">> => MockNode, + <<"opts">> => #{ http_client => httpc } + } + ], + <<"parallel">> => 1, + <<"responses">> => 1, + <<"stop-after">> => true, + <<"admissible-status">> => 200 + } + ] + }, + try + ?assertMatch( + {error, _}, + to_message( + <<"/tx/", TXID/binary>>, + <<"GET">>, + {ok, #{ <<"body">> => HeaderBody }}, + [], + Opts + ) + ) + after + hb_mock_server:stop(MockHandle) + end. + post_tx_json_two_node_test(Node1TxResponse, Node2TxResponse) -> {ok, MockNode1, MockHandle1} = hb_mock_server:start([ {"/tx", tx, Node1TxResponse} @@ -1521,6 +1586,40 @@ head_raw_tx_test() -> hb_maps:find(<<"header-length">>, Result, Opts) ). +head_raw_invalid_id_returns_not_found_test() -> + ?assertEqual( + {error, not_found}, + hb_ao:resolve( + #{ <<"device">> => <<"arweave@2.9">> }, + #{ + <<"path">> => <<"raw">>, + <<"raw">> => <<"lol">>, + <<"method">> => <<"HEAD">> + }, + #{} + ) + ). + +pending_invalid_offset_returns_invalid_offset_test() -> + {error, Error} = + hb_ao:resolve( + #{ <<"device">> => <<"arweave@2.9">> }, + #{ + <<"path">> => <<"pending">>, + <<"pending">> => <<"cat">>, + <<"offset">> => <<"dog">> + }, + #{} + ), + ?assertMatch( + #{ + <<"status">> := 400, + <<"content-type">> := <<"application/json">>, + <<"body">> := <<"{\"error\":\"invalid_offset\"}">> + }, + Error + ). + head_raw_ans104_test() -> Opts = setup_arweave_index_opts([]), DataItemID = <<"0vy2Ey8bWkSDcRIvWQJjxDeVGYOrTSmYIIhBILJntY8">>,