Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f80ed18
Rate limit restructure
khetzl Dec 15, 2025
56b7389
Add tests, sliding window under leaky bucket token bursts
khetzl Dec 17, 2025
80412c7
Move tests to separate file, avoid failed tests leaking
khetzl Dec 18, 2025
0af2d5f
Define new metrics for ar_limiter
khetzl Dec 18, 2025
99266c7
fix: apply metrics in ar_limiter, update tests to mock prometheus calls
khetzl Dec 18, 2025
9f999f9
Start ar_limit_sup
khetzl Dec 19, 2025
4cf6935
HTTP API Server now uses ar_limiter, and metrics collector
khetzl Dec 22, 2025
00476e2
fix: ar_limiter_sup:start_link/0
khetzl Dec 23, 2025
d8cecd0
fix: ar_limiter_sup:start_link/0
khetzl Dec 23, 2025
0cb8632
fix typo, test, and an interesting bug it hid
khetzl Dec 23, 2025
cca8147
Rework tests, cleanup sliding window timestamps, additional metrics
khetzl Dec 25, 2025
d9602b4
ar_limiter efault values to reflect similar numbers to what's in the …
khetzl Dec 26, 2025
fb9efeb
collector tests, add tests to github wf, minor cleanup
khetzl Dec 29, 2025
01c6dbc
limiter collector tests, hardwired config for metrics endpoint
khetzl Dec 29, 2025
bf2d29d
http iface tests, ar_config details trough ar_limiter_sup
khetzl Dec 30, 2025
ba6c789
Apply all config parameters to general limiter pool
khetzl Dec 31, 2025
ce49360
With limiter settings part of the config, ar_http_iface tests can be …
khetzl Dec 31, 2025
6644dd2
remove commented code
khetzl Dec 31, 2025
ee527f3
use the right metrics name for leaky bucket token use
khetzl Dec 31, 2025
ef64e2f
arweave_limiter as a self-contained Erlang app
khetzl Jan 1, 2026
744249e
rename tests in github action
khetzl Jan 1, 2026
0ffad03
fix module name
khetzl Jan 2, 2026
d593010
remove arweave_config and arweave_limiter from arweave.app.src as it …
khetzl Jan 5, 2026
d54afbe
start applications with their API functions, rather than with app.src…
khetzl Jan 6, 2026
be39ec7
further fixes to startup and metrics collector registration
khetzl Jan 6, 2026
63f490c
app.src and placeholders to fix tests
khetzl Jan 6, 2026
4d04b4f
rename limiter pools to groups
khetzl Jan 7, 2026
fc74273
peers per port, option to disable limiter group, and new group for lo…
khetzl Jan 7, 2026
08dfb82
remove limiting from ar_blacklist_middleware
khetzl Jan 8, 2026
7d8310c
Replicate previous default settings for different paths
khetzl Jan 9, 2026
4b8530a
route requrests to appropriate rate limiting groups
khetzl Jan 9, 2026
14e27e5
Remove commented code from bin/arweave startup script
khetzl Jan 9, 2026
828d716
Test config default values
khetzl Jan 12, 2026
49c7cf9
TEST -> AR_TEST
khetzl Jan 12, 2026
845c1be
RLG init function argument naming
khetzl Jan 12, 2026
2f69441
POSIX list in bin/arweave script
khetzl Jan 12, 2026
487ca7f
Add documentation for rate limiter middleware cowboy handler
khetzl Jan 12, 2026
daf5ab7
Remove redundant trap_exit
khetzl Jan 12, 2026
8af7f4b
Fix RLG State typos
khetzl Jan 12, 2026
7060f50
RLG reduce_for_peer tests
khetzl Jan 12, 2026
7e8e1a3
return config with warning log messages
khetzl Jan 14, 2026
954a4d2
fix config issue for limiter
khetzl Jan 14, 2026
6a9b5ca
Add comments for limiter code
khetzl Jan 14, 2026
7b5807b
More explaination
khetzl Jan 14, 2026
1a5d3ab
histogram to observer arweave_limiter_group performance
khetzl Jan 14, 2026
b000004
minor metrics related fixes, and tweaks, additional tests
khetzl Jan 16, 2026
9092ed6
limit based on IP
khetzl Jan 16, 2026
0ef5ce2
Handle reset_all for RLGs when monitored processes go down
khetzl Jan 16, 2026
3369c0d
Take RLG tick_reduction from config
khetzl Jan 16, 2026
2bee806
http iface limiter config_peer_to_ip_addr to convert anything into ip…
khetzl Jan 16, 2026
cde5bb4
Address config loading issues discovered by AI, allow parser to pick …
khetzl Jan 19, 2026
1629e91
Merge branch 'master' into rate_limiting_rework
khetzl Jan 19, 2026
acd08fd
fix tests again
khetzl Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/on-demand.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ on:
- ar_intervals
- ar_join
- ar_kv
- arweave_limiter_group
- arweave_limiter_metrics_collector
- ar_mempool_tests
- ar_merkle
- ar_mine_randomx_tests
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/x-test-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
ar_intervals,
ar_join,
ar_kv,
arweave_limiter_group,
arweave_limiter_metrics_collector,
ar_merkle,
ar_mining_cache,
ar_mining_server,
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/x-test-vdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
ar_intervals,
ar_join,
ar_kv,
arweave_limiter_group,
arweave_limiter_metrics_collector,
ar_merkle,
ar_node,
ar_node_utils,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ release/output
node_modules
screenlog.0
_*
*~
*.swp
7 changes: 7 additions & 0 deletions apps/arweave/src/ar.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,7 @@ start(normal, _Args) ->
%% Start the Prometheus metrics subsystem.
prometheus_registry:register_collector(prometheus_process_collector),
prometheus_registry:register_collector(ar_metrics_collector),
prometheus_registry:register_collector(arweave_limiter_metrics_collector),

%% Register custom metrics.
ar_metrics:register(),
Expand Down Expand Up @@ -1323,9 +1324,15 @@ stop(_State) ->
stop_dependencies() ->
?LOG_INFO("========== Stopping Arweave Node =========="),
{ok, [_Kernel, _Stdlib, _SASL, _OSMon | Deps]} = application:get_key(arweave, applications),
application:stop(arweave_limiter),
lists:foreach(fun(Dep) -> application:stop(Dep) end, Deps).

start_dependencies() ->
{ok, _Config} = arweave_config:get_env(),
%% We should start limiter automatically by having it as a dependency in the
%% .app.src, starting arweave_config should load the config, and arweave_limiter
%% should use arweave_config, rather than the legacy solution.
arweave_limiter:start(),
{ok, _} = application:ensure_all_started(arweave, permanent),
ok.

Expand Down
99 changes: 9 additions & 90 deletions apps/arweave/src/ar_blacklist_middleware.erl
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
-module(ar_blacklist_middleware).

-behaviour(cowboy_middleware).

-export([start/0, execute/2, reset/0, reset_rate_limit/3,
ban_peer/2, is_peer_banned/1, cleanup_ban/1, decrement_ip_addr/2]).
-export([start/0, ban_peer/2, is_peer_banned/1, cleanup_ban/1]).
-export([start_link/0]).

-ifdef(AR_TEST).
-export([reset/0]).
-endif.

-include_lib("arweave/include/ar.hrl").
-include_lib("arweave_config/include/arweave_config.hrl").
-include_lib("arweave/include/ar_blacklist_middleware.hrl").
-include_lib("eunit/include/eunit.hrl").

execute(Req, Env) ->
IPAddr = requesting_ip_addr(Req),
{ok, Config} = arweave_config:get_env(),
case lists:member(blacklist, Config#config.disable) of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I didn't realize we had a disable blacklist option, and I'm not sure if it is currently in use. It looks like it would completely disable rate limiting on the server side. @ldmberman @humaite are you aware of any nodes that use it?

It's possible some large miners use it on internal nodes used to server data to mining nodes (for example). But I'm not sure it would even be that effective since by default clients will self-throttle regardless of the server-side behavior.

This is a long way of asking:

  1. @khetzl did you mean to remove this option? (e.g. because you've replaced it with another configuration option)
  2. Even if the removal was unintentional... I'm not sure we actually need or want this particular flag. In general the enable and disable options are poorly understood and rarely used, so if we want to disable server-side rate limiting a more formal config is a better choice. Only question is whether anyone is using the current flag?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended to remove this, we shouldn't allow unrestricted access to resources to the public.
Trusted peers can be listed so their requests are limited via a less restrictive Rate Limiter Group. (Currently, no limiting at all)

Note: Even if the disable blacklist flag is enabled in the current release, a global concurrency limit is enforced by cowboy/ranch.

Copy link
Collaborator

@JamesPiechota JamesPiechota Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I agree it's correct to remove. However worth providing some context:

  • In cases like this where it is the right call to remove an option, we may still need to flag and communicate the removal for any miners that are relying on it. Both in the release notes and in Discord. I suspect this option isn't used much (or at all) so probably fine to silently remove.
  • Many miners implement their own security in front of their node. Specifically they will run nodes on a LAN without direct internet connection - so even if they opt in to using the disable blacklist flag it doesn't necessarily mean they or we are providing unrestricted access to the public.
  • I don't think this flag is used, but if it is used it is likely due to the above. We've had a few miners run large networks with many internal (non publicly accessible nodes). In the past they tried using local_peers but since arweave doesn't currently support any sort of wildcards/subnetting, and because the ndoe shutdown/boot time can take a long time, some of the miners had trouble managing rate limiting for their internal nodes. It's in that scenario where I could see disable blacklist being useful (setup an internal data-sharing node, disable blacklist, and then let all your other nodes connect to without worrying about needing to restart periodically to update local_peers)

All that said: I do think fine to remove. But I wanted to call out that context as our operating model is often different than that of non-blockchain companies (e.g. we build infrastructure software to be run by 3rd parties as well as by us internally - and those 3rd parties vary from novice to enterprise-level)

true ->
{ok, Req, Env};
_ ->
LocalIPs = [peer_to_ip_addr(Peer) || Peer <- Config#config.local_peers],
case lists:member(IPAddr, LocalIPs) of
true ->
{ok, Req, Env};
false ->
case increment_ip_addr(IPAddr, Req) of
{block, Limit} -> {stop, blacklisted(Limit, Req)};
pass -> {ok, Req, Env}
end
end
end.

start_link() ->
{ok, spawn_link(fun() -> start() end)}.

Expand All @@ -43,6 +25,10 @@ start() ->
#{ skip_on_shutdown => false }
).

reset() ->
true = ets:delete_all_objects(?MODULE),
ok.

%% Ban a peer completely for TTLSeconds seoncds. Since we cannot trust the port,
%% we ban the whole IP address.
ban_peer(Peer, TTLSeconds) ->
Expand Down Expand Up @@ -83,71 +69,4 @@ cleanup_ban(TableID) ->
end.

%private functions
blacklisted(Limit, Req) ->
cowboy_req:reply(
429,
#{
<<"connection">> => <<"close">>,
<<"retry-after">> => integer_to_binary(?THROTTLE_PERIOD div 1000),
<<"x-rate-limit-limit">> => integer_to_binary(Limit)
},
<<"Too Many Requests">>,
Req
).

reset() ->
true = ets:delete_all_objects(?MODULE),
ok.

reset_rate_limit(TableID, IPAddr, Path) ->
case ets:whereis(?MODULE) of
TableID ->
ets:delete(?MODULE, {rate_limit, IPAddr, Path});
_ ->
table_owner_died
end.

increment_ip_addr(IPAddr, Req) ->
case ets:whereis(?MODULE) of
undefined -> pass;
_ -> update_ip_addr(IPAddr, Req, 1)
end.

decrement_ip_addr(IPAddr, Req) ->
case ets:whereis(?MODULE) of
undefined -> pass;
_ -> update_ip_addr(IPAddr, Req, -1)
end.

update_ip_addr(IPAddr, Req, Delta) ->
{PathKey, Limit} = get_key_limit(IPAddr, Req),
%% Divide by 2 as the throttle period is 30 seconds.
RequestLimit = Limit div 2,
Key = {rate_limit, IPAddr, PathKey},
case ets:update_counter(?MODULE, Key, {2, Delta}, {Key, 0}) of
1 ->
_ = ar_timer:apply_after(
?THROTTLE_PERIOD,
?MODULE,
reset_rate_limit,
[ets:whereis(?MODULE), IPAddr, PathKey],
#{ skip_on_shutdown => true }
),
pass;
Count when Count =< RequestLimit ->
pass;
_ ->
{block, Limit}
end.

requesting_ip_addr(Req) ->
{IPAddr, _} = cowboy_req:peer(Req),
IPAddr.

peer_to_ip_addr({A, B, C, D, _}) -> {A, B, C, D}.

get_key_limit(IPAddr, Req) ->
Path = ar_http_iface_server:split_path(cowboy_req:path(Req)),
{ok, Config} = arweave_config:get_env(),
Map = maps:get(IPAddr, Config#config.requests_per_minute_limit_by_ip, #{}),
?RPM_BY_PATH(Path, Map)().
Loading
Loading