Conversation
3fc613f to
954250a
Compare
f8e13ba to
79c1309
Compare
bf28b5e to
7a24c4b
Compare
bfa2e73 to
abcce9d
Compare
abcce9d to
58f5705
Compare
58f5705 to
05846c5
Compare
mine_one_block was changed from a task queue cast to a synchronous gen_server:call in a176655 to return {error, mining_server_running}. This bypassed the task queue's is_joined guard, introducing a race condition: after ar_test_node:join_on, the gen_server is accepting calls before the join handler populates the ETS state, so get_current_diff crashes with badarith on undefined + 1. Revert mine_one_block to a cast routed through the task queue, restoring the original design from 96ed594. No caller checks the return value. The task queue's existing is_joined check naturally prevents execution before the node state is initialized.
meck's internal gen_server proxy uses gen_server:call/2 with the default
5s timeout, which can fire under CI load. Catch the exit:{timeout, _} so
the do_until loop retries instead of crashing the test. Also bump the
outer do_until timeout from 60s to 120s.
application:stop(arweave) can hang if a supervised process is stuck (e.g. mid-VDF computation), eating into the eunit test timeout and failing unrelated tests. Wrap the call with a 60s deadline and force-kill the application master if it exceeds it.
wait_until_height/4 made two separate calls: do_wait_until_height (which polls until height >= target) followed by ar_node:get_height(). The node could mine additional blocks between these calls, causing a strict assertEqual to fail (e.g. expected 14, got 16). Derive the height from the block index already returned by do_wait_until_height and use a >= assertion consistent with the wait condition.
Stop application dependencies in reverse order to avoid
crashing dependents (e.g. cowboy) before their dependencies (e.g. ranch).
Also handle {not_started, arweave} in stop/0 which crashes when
clean_up_and_stop is called before arweave has been started.
Add prometheus:start(), arweave_config:start(), and wait_until_joined() to match the startup sequence used by start_node/3 and prevent the node from being used before it finishes joining.
The retry-closed-connection change (902b9bc) broadened the error catch to all {error, _} tuples, unintentionally converting errors like too_much_data and timeout into client_error.
Start the validator node before the exit node so its HTTP server is available when peers validate it during startup. This is important when running tests sequentially via ./bin/test, where a previous test module may have left arweave stopped on the main node.
| StorageModules = | ||
| case Config#config.storage_modules of | ||
| [] -> | ||
| [{21 * ?MiB, 0, {replica_2_9, MiningAddr}}]; |
There was a problem hiding this comment.
An particular reason for 21 MiB? Or not important?
| %% @doc Read recent block heads, tx headers, block time history, and account tree data | ||
| %% from the snapshot directory and store them in the data directory. Does not do anything | ||
| %% if recent blocks are already available locally. | ||
| store_snapshot_data2(SnapshotDir) -> |
There was a problem hiding this comment.
I don't see a store_snapshot_data should this be renamed to submit_snapshot_data2?
| reward_history_bi(Height, BI) -> | ||
| InterimRewardHistoryLength0 = (Height - ar_fork:height_2_8()) + 21600, | ||
| InterimRewardHistoryLength = | ||
| case InterimRewardHistoryLength0 > 0 of | ||
| true -> | ||
| InterimRewardHistoryLength0; | ||
| false -> | ||
| 0 | ||
| end, | ||
| RewardHistoryBI0 = ar_rewards:trim_buffered_reward_history(Height, BI), | ||
| lists:sublist(RewardHistoryBI0, InterimRewardHistoryLength). |
There was a problem hiding this comment.
Can this be deduped with: https://github.com/ArweaveTeam/arweave/blob/lb/localnet/apps/arweave/src/ar_node_worker.erl#L1833-L1837
| %% @doc Format a packing type tuple into a human-readable string for logging. | ||
| format_packing({spora_2_6, Addr}) -> | ||
| io_lib:format("spora_2_6(~s)", [ar_util:encode(Addr)]); | ||
| format_packing({composite, Addr, Diff}) -> | ||
| io_lib:format("composite(~s, ~B)", [ar_util:encode(Addr), Diff]); | ||
| format_packing({replica_2_9, Addr}) -> | ||
| io_lib:format("(replica_2_9, ~s)", [ar_util:encode(Addr)]); | ||
| format_packing(unpacked) -> | ||
| "unpacked"; | ||
| format_packing(Other) -> | ||
| io_lib:format("~p", [Other]). | ||
|
|
There was a problem hiding this comment.
Possible to dedupe with ar_serialize:encode_packing?
| %% paths and data roots, register them with ar_data_root_sync, then write the | ||
| %% raw transaction data to storage. | ||
| %% Return {TotalBigChunkBytes, TotalSmallChunkBytes}. | ||
| submit_block_data(BlockStart, TXs) -> |
There was a problem hiding this comment.
For these submit_xxx functions is the reason we call modules directly rather than going through the "normal" HTTP API for performance reasons? Or maybe because we're loading a snapshot from disk rather than normal syncing the normal API would reject the requests?
| %% data included in the localnet snapshot. These are real mainnet transactions | ||
| %% whose data is bundled in the snapshot's seed_txs/ directory so the localnet | ||
| %% node has chunk data to mine with. | ||
| snapshot_txs() -> |
There was a problem hiding this comment.
Are the selected block height special? Or just picked to divide the blockchain into N roughly equal snapshots?
| {origin_tx_map, OriginTXMap2} | ||
| {origin_tx_map, OriginTXMap2}, | ||
| {origin_spent_total_map, OriginSpentTotalMap2}, | ||
| {origin_spent_total_denomination, Denomination} |
There was a problem hiding this comment.
I suspect it's not really important, but Denomination may still include the impact of TXs that were dropped in the loop above, right? Given how seldom denomination changes (if ever), my guess is this is not relevant in practice. Just trying to confirm my understanding
| case Config#config.mine of | ||
| true -> | ||
| gen_server:cast(?MODULE, automine); | ||
| gen_server:cast(?MODULE, start_mining); |
There was a problem hiding this comment.
| gen_server:cast(?MODULE, start_mining); | |
| start_mining(); |
| -ifdef(LOCALNET). | ||
| -define(MINING_SERVER, ar_localnet_mining_server). | ||
| -else. | ||
| -define(MINING_SERVER, ar_mining_server). |
There was a problem hiding this comment.
I still see ar_mining_server referenced explicitly in other modules - that okay? i.e. is it only here that we need to swap ar_localnet_mining_server for ar_mining_server?
There was a problem hiding this comment.
Ah it's only when these functions are called that we need to swap things out:
-callback start_mining({DiffPair, MerkleRebaseThreshold, Height}) -> ok when
DiffPair :: {non_neg_integer() | infinity, non_neg_integer() | infinity},
MerkleRebaseThreshold :: non_neg_integer() | infinity,
Height :: non_neg_integer().
-callback pause() -> ok.
-callback is_paused() -> boolean().
-callback set_difficulty(DiffPair :: {non_neg_integer() | infinity, non_neg_integer() | infinity}) -> ok.
-callback set_merkle_rebase_threshold(Threshold :: non_neg_integer() | infinity) -> ok.
-callback set_height(Height :: integer()) -> ok.
Note
High Risk
Touches mining/nonce-limiter validation, recall-range computation, storage/rocksdb open semantics, and mempool overspend logic; regressions could impact consensus validation, syncing, or node startup from state snapshots.
Overview
Adds a LOCALNET runtime mode and tooling to run a single-node, snapshot-seeded network. This introduces
ar_localnet(start from a snapshot dir, seed data roots/tx data, and create new snapshots) plus alocalnet_shellentrypoint and a dedicatedar_localnet_mining_server/supervisor wired viaar_node_workerto support deterministic “mine one block / mine until height” workflows.Extends “start from state” to accept an explicit
start_from_statefolder (CLI/config), validates it againstdata_dir, and updates startup/join logic to read block index/history/wallet state from that folder; related changes includear_node:read_recent_blocks/3, disk-cache lookups with an optional custom directory, and multiple DB open callsites updated to use the newar_kvAPI.Refactors
ar_kvdatabase opening to a map-basedopen/1(plusopen_readonly/1andclose/1) with explicit paths/log paths and read-only support, then updates storage/sync components accordingly; also adds a synchronousstore_data_roots_sync/4path and a new HTTPPOST /data_roots/{offset}endpoint to accept validated data-root indices.Makes LOCALNET-specific consensus relaxations for testing (e.g., optional precalculated recall ranges, step-count/timeline comparisons, and difficulty checks), and improves mempool overspend filtering by tracking per-origin spent totals (with denomination-aware redenomination) to drop the lowest-priority spends when an origin exceeds confirmed balance.
CI/dev tooling updates include enabling Git LFS on checkouts (and tracking
localnet_snapshot/**via LFS), adding headless notebook jobs to the full test workflow, and adding Jupyter config to strip notebook outputs by default.Written by Cursor Bugbot for commit 94d7166. This will update automatically on new commits. Configure here.