From 926caa7c07a54c9220d7d95374bb6dfc67f81ad3 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Tue, 23 Dec 2025 02:11:41 +0000 Subject: [PATCH 1/3] wip: Allow to not unbundle bundle tx --- src/ar_bundles.erl | 38 ++++++++++++++++++++++++-------------- src/dev_codec_ans104.erl | 2 +- src/hb_http.erl | 4 ++-- src/hb_opts.erl | 9 +++++++-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/ar_bundles.erl b/src/ar_bundles.erl index 3f070b54a..d873ba6fc 100644 --- a/src/ar_bundles.erl +++ b/src/ar_bundles.erl @@ -3,7 +3,7 @@ -export([id/1, id/2, hd/1, member/2, find/2]). -export([new_item/4, sign_item/2, verify_item/1]). -export([encode_tags/1, decode_tags/1]). --export([serialize/1, deserialize/1, serialize_bundle/3]). +-export([serialize/1, deserialize/2, serialize_bundle/3]). -export([data_item_signature_data/1]). -include("include/hb.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -396,13 +396,13 @@ encode_vint(ZigZag, Acc) -> %% and *not* a bundle. It may be an item that contains a bundle, though. %% When deserializing a #tx it is the #tx.data that is deserialized (after %% consulting the #tx.tags to confirm that data format). -deserialize(not_found) -> throw(not_found); -deserialize(Item) when is_record(Item, tx) -> - maybe_unbundle(Item); -deserialize(Binary) -> - deserialize_item(Binary). - -deserialize_item(Binary) -> +deserialize(Item) -> deserialize(Item, #{}). +deserialize(not_found, _Opts) -> throw(not_found); +deserialize(Item, Opts) when is_record(Item, tx) -> + maybe_unbundle(Item, Opts); +deserialize(Binary, Opts) -> + deserialize_item(Binary, Opts). +deserialize_item(Binary, Opts) -> {SignatureType, Signature, Owner, Rest} = decode_signature(Binary), {Target, Rest2} = decode_optional_field(Rest), {Anchor, Rest3} = decode_optional_field(Rest2), @@ -418,14 +418,24 @@ deserialize_item(Binary) -> tags = Tags, data = Data, data_size = byte_size(Data) - }) + }), + Opts ). -maybe_unbundle(Item) -> +maybe_unbundle(Item, Opts) -> case dev_arweave_common:type(Item) of - list -> unbundle_list(Item); - binary -> Item; - map -> unbundle_map(Item) + list -> + UnbundleBundles = hb_opts:get(<<"unbundle_bundles">>, true, Opts), + case UnbundleBundles of + true -> + unbundle_list(Item); + false -> + Item + end; + binary -> + Item; + map -> + unbundle_map(Item) end. unbundle_list(Item) -> @@ -475,7 +485,7 @@ decode_bundle_items([], <<>>) -> []; decode_bundle_items([{_ID, Size} | RestItems], ItemsBin) -> [ - deserialize_item(binary:part(ItemsBin, 0, Size)) + deserialize_item(binary:part(ItemsBin, 0, Size), #{}) | decode_bundle_items( RestItems, diff --git a/src/dev_codec_ans104.erl b/src/dev_codec_ans104.erl index 5b90ad154..d149dcfd4 100644 --- a/src/dev_codec_ans104.erl +++ b/src/dev_codec_ans104.erl @@ -88,7 +88,7 @@ from(TX, Req, Opts) when is_record(TX, tx) -> end. do_from(RawTX, Req, Opts) -> % Ensure the TX is fully deserialized. - TX = ar_bundles:deserialize(dev_arweave_common:normalize(RawTX)), + TX = ar_bundles:deserialize(dev_arweave_common:normalize(RawTX), Opts), ?event({from, {parsed_tx, TX}}), % Get the fields, tags, and data from the TX. Fields = dev_codec_ans104_from:fields(TX, <<>>, Opts), diff --git a/src/hb_http.erl b/src/hb_http.erl index 2f20d1764..a40b7f966 100644 --- a/src/hb_http.erl +++ b/src/hb_http.erl @@ -230,7 +230,7 @@ outbound_result_to_message(<<"ans104@1.0">>, Status, Headers, Body, Opts) -> {result_is_ans104, {headers, Headers}, {body, Body}}, Opts ), - try ar_bundles:deserialize(Body) of + try ar_bundles:deserialize(Body, Opts) of Deserialized -> { response_status_to_atom(Status), @@ -869,7 +869,7 @@ req_to_tabm_singleton(Req, Body, Opts) -> ), httpsig_to_tabm_singleton(PrimitiveMsg, Req, Body, Opts); <<"ans104@1.0">> -> - Item = ar_bundles:deserialize(Body), + Item = ar_bundles:deserialize(Body, Opts), ?event(debug_accept, {deserialized_ans104, {item, Item}, diff --git a/src/hb_opts.erl b/src/hb_opts.erl index e5b49c2b5..850dc4470 100644 --- a/src/hb_opts.erl +++ b/src/hb_opts.erl @@ -314,11 +314,16 @@ default_message() -> <<"value">> => <<"ao">> } ], - <<"local-store">> => [?DEFAULT_PRIMARY_STORE] + <<"local-store">> => [?DEFAULT_PRIMARY_STORE], + %% By default bundle transactions will be unbundled. This can take some + %% time depending on the bundle size. To avoid the unbundle, set this + %% to false. + <<"unbundle_bundles">> => false }, #{ <<"store-module">> => hb_store_gateway, - <<"local-store">> => [?DEFAULT_PRIMARY_STORE] + <<"local-store">> => [?DEFAULT_PRIMARY_STORE], + <<"unbundle_bundles">> => false } ], priv_store => From 27ea2cbee4c7167aa4dd527626a90ee29dc9405a Mon Sep 17 00:00:00 2001 From: speeddragon Date: Tue, 23 Dec 2025 18:32:57 +0000 Subject: [PATCH 2/3] test: Add test --- src/ar_bundles.erl | 28 +++++++++++++++++++++++++++- src/dev_codec_tx.erl | 2 +- src/hb_opts.erl | 4 ++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/ar_bundles.erl b/src/ar_bundles.erl index d873ba6fc..539e14f2d 100644 --- a/src/ar_bundles.erl +++ b/src/ar_bundles.erl @@ -825,7 +825,7 @@ serialize_deserialize_deep_signed_bundle_test() -> ?assert(verify_item(Item3)). %% @doc Deserialize and reserialize a data item produced by the arbundles JS -%% library. This validates both that we can read an arbundles.js data itme +%% library. This validates both that we can read an arbundles.js data item %% but also that our data item serialization code is compatible with it. arbundles_item_roundtrip_test() -> {ok, Bin} = file:read_file(<<"test/arbundles.js/ans104-item.bundle">>), @@ -891,6 +891,32 @@ arbundles_list_bundle_roundtrip_test() -> ?assertEqual(Bin, Reserialized#tx.data), ok. +%% @doc Do not unbundle bundles if store option <<"unbundle_bundles">> +%% is set to false +arbundles_list_bundle_not_unbundled_roundtrip_test() -> + W = ar_wallet:new(), + {ok, Bin} = file:read_file(<<"test/arbundles.js/ans104-list-bundle.bundle">>), + TX = sign_item(#tx{ + format = ans104, + data = Bin, + data_size = byte_size(Bin), + tags = ?BUNDLE_TAGS + }, W), + ?event(debug_test, {tx, {explicit, TX}}), + ?assert(verify_item(TX)), + + Opts = #{<<"unbundle_bundles">> => false}, + Deserialized = deserialize(TX, Opts), + ?assert(is_binary(Deserialized#tx.data)), + ?assert(binary:match(Deserialized#tx.data, <<"first">>) /= nomatch), + ?assert(binary:match(Deserialized#tx.data, <<"second">>) /= nomatch), + ?assert(binary:match(Deserialized#tx.data, <<"third">>) /= nomatch), + + Reserialized = dev_arweave_common:normalize(Deserialized), + ?assert(verify_item(Reserialized)), + ?assertEqual(Bin, Reserialized#tx.data), + ok. + arbundles_single_list_bundle_roundtrip_test() -> W = ar_wallet:new(), {ok, Bin} = file:read_file(<<"test/arbundles.js/ans104-single-list-bundle.bundle">>), diff --git a/src/dev_codec_tx.erl b/src/dev_codec_tx.erl index 3363969d2..a2f4939dc 100644 --- a/src/dev_codec_tx.erl +++ b/src/dev_codec_tx.erl @@ -75,7 +75,7 @@ do_from(RawTX, Req, Opts) -> % Assert a minimally valid TX record so we can avoid a lot of edge case % handling in the rest of the code. enforce_valid_tx(RawTX), - TX = ar_bundles:deserialize(dev_arweave_common:normalize(RawTX)), + TX = ar_bundles:deserialize(dev_arweave_common:normalize(RawTX), Opts), ?event({from, {parsed_tx, hb_util:human_id(TX#tx.id)}}), % Get the fields, tags, and data from the TX. Fields = dev_codec_tx_from:fields(TX, <<>>, Opts), diff --git a/src/hb_opts.erl b/src/hb_opts.erl index 850dc4470..02935d637 100644 --- a/src/hb_opts.erl +++ b/src/hb_opts.erl @@ -318,12 +318,12 @@ default_message() -> %% By default bundle transactions will be unbundled. This can take some %% time depending on the bundle size. To avoid the unbundle, set this %% to false. - <<"unbundle_bundles">> => false + <<"unbundle_bundles">> => true }, #{ <<"store-module">> => hb_store_gateway, <<"local-store">> => [?DEFAULT_PRIMARY_STORE], - <<"unbundle_bundles">> => false + <<"unbundle_bundles">> => true } ], priv_store => From f94fe7bea5a99ab45ce37b3f7239ea65db7bab5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Magalh=C3=A3es?= Date: Mon, 2 Feb 2026 17:15:41 +0000 Subject: [PATCH 3/3] fix: Bug --- src/ar_bundles.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ar_bundles.erl b/src/ar_bundles.erl index 539e14f2d..8cba861ec 100644 --- a/src/ar_bundles.erl +++ b/src/ar_bundles.erl @@ -3,7 +3,7 @@ -export([id/1, id/2, hd/1, member/2, find/2]). -export([new_item/4, sign_item/2, verify_item/1]). -export([encode_tags/1, decode_tags/1]). --export([serialize/1, deserialize/2, serialize_bundle/3]). +-export([serialize/1, deserialize/1, deserialize/2, serialize_bundle/3]). -export([data_item_signature_data/1]). -include("include/hb.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -1027,4 +1027,4 @@ generate_and_write_map_bundle_test_disabled() -> ?event(debug_test, {deserialized, {explicit, Deserialized}}), ?assert(verify_item(Deserialized)), ok = file:write_file( - <<"test/arbundles.js/ans104-map-bundle-erlang.bundle">>, Serialized). \ No newline at end of file + <<"test/arbundles.js/ans104-map-bundle-erlang.bundle">>, Serialized).