dev_query_arweave.erl - Arweave GraphQL Query Device
Overview
Purpose: Implement Arweave GraphQL API queries within the ~query@1.0 device
Module: dev_query_arweave
Parent Device: query@1.0
Query Types: Transactions, Blocks, and their fields
This module provides GraphQL-style query capabilities for Arweave data stored in the local cache. It supports querying transactions and blocks by various criteria including IDs, tags, owners, recipients, and block heights.
Supported Query Arguments
-define(SUPPORTED_QUERY_ARGS,
[
<<"height">>, % Block height range
<<"id">>, % Single transaction/block ID
<<"ids">>, % Multiple IDs
<<"tags">>, % Tag filters
<<"owners">>, % Owner address filters
<<"recipients">> % Recipient address filters
]
).Dependencies
- HyperBEAM:
hb_ao,hb_cache,hb_store,hb_opts,hb_message,hb_util,hb_maps,hb_crypto - Query:
dev_query_graphql - Arweave:
dev_arweave_block_cache,dev_codec_httpsig_keyid - Testing:
eunit - Includes:
include/hb.hrl
Public Functions Overview
%% Query Handler
-spec query(Object, Field, Args, Opts) -> {ok, Result}.Public Functions
query/4
-spec query(Object, Field, Args, Opts) -> {ok, Result}
when
Object :: map() | list() | term(),
Field :: binary(),
Args :: map(),
Opts :: map(),
Result :: term().Description: Handle Arweave GraphQL queries for transactions, blocks, and their fields. Supports nested field resolution following GraphQL patterns.
Test Code:-module(dev_query_arweave_test).
-include_lib("eunit/include/eunit.hrl").
query_edges_test() ->
List = [#{<<"a">> => 1}, #{<<"b">> => 2}],
{ok, Edges} = dev_query_arweave:query(List, <<"edges">>, #{}, #{}),
?assertEqual([{ok, #{<<"a">> => 1}}, {ok, #{<<"b">> => 2}}], Edges).
query_node_test() ->
Msg = #{<<"data">> => <<"test">>},
{ok, Result} = dev_query_arweave:query(Msg, <<"node">>, #{}, #{}),
?assertEqual(Msg, Result).
query_block_fields_test() ->
Block = #{
<<"previous_block">> => <<"prev-hash">>,
<<"height">> => 12345,
<<"timestamp">> => 1234567890
},
{ok, Prev} = dev_query_arweave:query(Block, <<"previous">>, #{}, #{}),
?assertEqual(<<"prev-hash">>, Prev),
{ok, Height} = dev_query_arweave:query(Block, <<"height">>, #{}, #{}),
?assertEqual(12345, Height),
{ok, Timestamp} = dev_query_arweave:query(Block, <<"timestamp">>, #{}, #{}),
?assertEqual(1234567890, Timestamp).
query_owner_fields_test() ->
Owner = #{ <<"key">> => <<"pubkey">>, <<"address">> => <<"addr">> },
{ok, Key} = dev_query_arweave:query(Owner, <<"key">>, #{}, #{}),
?assertEqual(<<"pubkey">>, Key),
{ok, Addr} = dev_query_arweave:query(Owner, <<"address">>, #{}, #{}),
?assertEqual(<<"addr">>, Addr).
query_winston_test() ->
{ok, Amount} = dev_query_arweave:query(12345, <<"winston">>, #{}, #{}),
?assertEqual(12345, Amount).
query_data_fields_test() ->
DataObj = #{ <<"data">> => <<"hello world">>, <<"type">> => <<"text/plain">> },
{ok, Size} = dev_query_arweave:query(DataObj, <<"size">>, #{}, #{}),
?assertEqual(11, Size),
{ok, Type} = dev_query_arweave:query(DataObj, <<"type">>, #{}, #{}),
?assertEqual(<<"text/plain">>, Type).Supported Fields
Collection Queries
transactions / transaction
Query for transactions matching specified criteria.
query(Obj, <<"transactions">>, Args, Opts) -> {ok, [Message]}.
query(Obj, <<"transaction">>, Args, Opts) -> {ok, Message | null}.blocks / block
Query for blocks matching specified criteria.
query(Obj, <<"blocks">>, Args, Opts) -> {ok, [Block]}.
query(Obj, <<"block">>, Args, Opts) -> {ok, Block | null}.edges / node
Navigate GraphQL edge/node structure.
query(List, <<"edges">>, Args, Opts) -> {ok, [{ok, Msg} || Msg <- List]}.
query(Msg, <<"node">>, Args, Opts) -> {ok, Msg}.Block Fields
previous
Get the previous block hash.
query(Block, <<"previous">>, _Args, Opts) -> {ok, PreviousBlockHash | null}.height
Get the block height.
query(Block, <<"height">>, _Args, Opts) -> {ok, Height | null}.timestamp
Get the block timestamp.
query(Block, <<"timestamp">>, _Args, Opts) -> {ok, Timestamp | null}.Transaction Fields
signature
Get the transaction signature from commitments.
query(Msg, <<"signature">>, _Args, Opts) -> {ok, Signature | null}.owner
Get the owner address and public key.
query(Msg, <<"owner">>, _Args, Opts) -> {ok, #{ <<"address">> => Addr, <<"key">> => Key } | null}.key / address
Extract key or address from owner object.
query(#{ <<"key">> := Key }, <<"key">>, _Args, _Opts) -> {ok, Key}.
query(#{ <<"address">> := Address }, <<"address">>, _Args, _Opts) -> {ok, Address}.fee / quantity
Get transaction fee or quantity (in Winston).
query(Msg, <<"fee">>, _Args, Opts) -> {ok, Fee}.
query(Msg, <<"quantity">>, _Args, Opts) -> {ok, Quantity}.
query(Number, <<"winston">>, _Args, _Opts) -> {ok, Number}.recipient
Get the transaction recipient.
query(Msg, <<"recipient">>, _Args, Opts) -> {ok, Recipient | <<"">>}.anchor
Get the transaction anchor (last transaction hash).
query(Msg, <<"anchor">>, _Args, Opts) -> {ok, Anchor | <<"">>}.data
Get transaction data with content type.
query(Msg, <<"data">>, _Args, Opts) -> {ok, #{ <<"data">> => Data, <<"type">> => ContentType }}.size / type
Get data size or content type from data object.
query(#{ <<"data">> := Data }, <<"size">>, _Args, _Opts) -> {ok, byte_size(Data)}.
query(#{ <<"type">> := Type }, <<"type">>, _Args, _Opts) -> {ok, Type}.Query Matching
match/3
Generates matches for each supported query argument.
| Argument | Description | Match Method |
|---|---|---|
height | Block height range | Enumerate heights, check store |
id | Single ID | Direct lookup |
ids | Multiple IDs | Direct lookup list |
tags | Tag key-value pairs | Cache template match |
owners | Owner addresses | Commitment field match |
recipients | Recipient addresses | Commitment field match |
Common Patterns
%% Query transactions by tag
Args = #{
<<"tags">> => [
#{ <<"name">> => <<"App-Name">>, <<"values">> => [<<"MyApp">>] },
#{ <<"name">> => <<"Type">>, <<"values">> => [<<"Message">>] }
]
},
{ok, Transactions} = dev_query_arweave:query(#{}, <<"transactions">>, Args, Opts).
%% Query single transaction by ID
{ok, TX} = dev_query_arweave:query(#{}, <<"transaction">>, #{ <<"id">> => TXID }, Opts).
%% Query blocks by height range
{ok, Blocks} = dev_query_arweave:query(
#{},
<<"blocks">>,
#{ <<"height">> => #{ <<"min">> => 1000000, <<"max">> => 1000010 } },
Opts
).
%% Query by owner address
{ok, OwnerTXs} = dev_query_arweave:query(
#{},
<<"transactions">>,
#{ <<"owners">> => [OwnerAddress] },
Opts
).
%% Navigate GraphQL-style response
{ok, Transactions} = dev_query_arweave:query(#{}, <<"transactions">>, Args, Opts),
{ok, Edges} = dev_query_arweave:query(Transactions, <<"edges">>, #{}, Opts),
lists:foreach(
fun({ok, Edge}) ->
{ok, Node} = dev_query_arweave:query(Edge, <<"node">>, #{}, Opts),
{ok, Owner} = dev_query_arweave:query(Node, <<"owner">>, #{}, Opts),
{ok, Address} = dev_query_arweave:query(Owner, <<"address">>, #{}, Opts),
io:format("TX from: ~s~n", [Address])
end,
Edges
).
%% Get transaction data with metadata
{ok, TX} = dev_query_arweave:query(#{}, <<"transaction">>, #{ <<"id">> => ID }, Opts),
{ok, DataObj} = dev_query_arweave:query(TX, <<"data">>, #{}, Opts),
{ok, Size} = dev_query_arweave:query(DataObj, <<"size">>, #{}, Opts),
{ok, Type} = dev_query_arweave:query(DataObj, <<"type">>, #{}, Opts).Store Scoping
The query scope can be configured to limit which stores are searched:
%% Default: local store only
Opts = #{ query_arweave_scope => [local] }.
%% Include remote stores
Opts = #{ query_arweave_scope => [local, remote] }.ID Resolution
The module handles multiple ID types for messages:
- Base ID: Unsigned message ID
- Commitment IDs: IDs derived from signatures
- Store Paths: Resolved filesystem paths
%% Find all IDs for a message
all_ids(ID, Opts) ->
Store = hb_opts:get(store, no_store, Opts),
case hb_store:list(Store, << ID/binary, "/commitments">>) of
{ok, []} -> [ID];
{ok, CommitmentIDs} -> CommitmentIDs;
_ -> []
end.References
- GraphQL Device -
dev_query_graphql.erl - Query Device -
dev_query.erl - Block Cache -
dev_arweave_block_cache.erl - Cache System -
hb_cache.erl - Message Handling -
hb_message.erl
Notes
- GraphQL Compatibility: Follows Arweave GraphQL API patterns
- Local Cache: Queries local cache, not Arweave network directly
- Commitment Fields: Owner/recipient extracted from message commitments
- Null Handling: Returns
nullfor missing optional fields - Height Range: Block height queries use min/max range
- Tag Matching: Uses
hb_cache:match/2with template patterns - ID Intersection: Multiple query args produce intersection of results
- Edge/Node Pattern: Supports GraphQL-style connection pagination
- Data Fallback: Checks both
dataandbodyfields for content - Store Scoping: Configurable store scope for queries