Skip to content

perf: share the RPC route table across HTTP connections - #7673

Open
ccipherx wants to merge 8 commits into
stacks-network:mainfrom
ccipherx:perf/rpc-route
Open

ccipherx wants to merge 8 commits into
stacks-network:mainfrom
ccipherx:perf/rpc-route

Conversation

@ccipherx

@ccipherx ccipherx commented Sep 27, 2026 •

Copy link
Copy Markdown

Description

each inbound http connection rebuilt the rpc route table, registering 52 handlers and compiling 156 regexes (~7 ms per p2p connection).

HttpPeer now builds the route table once from ConnectionOptions and reuses it across connections.

  • HttpRequest::verb, path_regex, and path_regex_permissive are associated functions with where Self: Sized, keeping the trait object-safe.
  • RPCRouteTable::new(&ConnectionOptions) builds routes and handlers, HttpPeer owns the table and passes it to ConversationHttp.
  • StacksHttp::with_routes shares compiled routes via Arc<[RPCRoute]> while creating fresh handlers per connection.
  • register_rpc_methods keeps the same body and order, now on RPCRoutesBuilder.
  • StacksHttp::new(addr, &conn_opts) remains for tests and one-off use.

Performance

Operation Median p99
StacksHttp::new 6.89 ms 7.61 ms
RPCRouteTable::new 6.95 ms 9.00 ms
StacksHttp::with_routes 871 ns 1.06 µs
New connection + GET /v2/info 3.39 µs 5.53 µs
GET /v2/info on open connection 2.78 µs 4.49 µs
404 route scan 3.90 µs 6.37 µs

Shared regexes retain their match caches, so new connections avoid the previous cold-start cost.

Tests

  • Added coverage for per-connection handler state and settings.

Applicable issues

Additional info (benefits, drawbacks, caveats)

  • Rust API changes:
    • HttpRequest::verb, path_regex and path_regex_permissive no longer take &self.
    • ConversationHttp::new takes &RPCRouteTable.
    • StacksHttp::new_client no longer takes conn_opts.
    • StacksHttp no longer exposes handler configuration fields used only during construction.
  • HttpPeer builds its table from connection_opts at construction. Changing those options afterwards does not update existing handlers.

Checklist

  • Test coverage for new or modified code paths
  • For new Clarity features or consensus changes, add property tests (see docs/property-testing.md)
  • Changelog fragment(s) or "no changelog" label added (see changelog.d/README.md). If this PR breaks anything for node operators or users, or requires them to manually do anything (such as adjust a setting), use the breaking category.
  • Required documentation changes (e.g., rpc/openapi.yaml for RPC endpoints, event-dispatcher.md for new events)
  • New clarity functions have corresponding PR in clarity-benchmarking repo

@CLAassistant

CLAassistant commented Sep 27, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@benjamin-stacks benjamin-stacks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you so much for the contribution, I think we should absolutely pull this in!

I do however have concerns about putting the table in a global singleton, and about the possibilities of dependencies for the regexes. See my inline comment.

Do you think you might be able to address that?

Comment thread stackslib/src/net/httpcore.rs Outdated

@benjamin-stacks benjamin-stacks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work, thank you! Just one small nit.

Adding @federico-stacks as a second reviewer.

Comment thread stackslib/src/net/httpcore.rs Outdated
/// The read-only RPC memory limit must be threaded from `ConnectionOptions`
/// into the HTTP server state so the handlers can set the correct budget.
#[test]
fn test_read_only_call_max_mem_bytes_threaded_into_http() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While I don't like that we're removing this test without replacement, I understand that it's tricky to find a good way to test this now. I think it's justifiable.

Co-authored-by: benjamin-stacks <246469650+benjamin-stacks@users.noreply.github.com>

@federico-stacks federico-stacks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! I've just added one nit.

A couple of other things to flag:

  • The changelog entry for this change is missing. You can add one following these guidelines.
  • There are a few merge conflicts that need to be resolved.

Comment thread stackslib/src/net/httpcore.rs Outdated
@benjamin-stacks benjamin-stacks self-assigned this Oct 14, 2026

@federico-stacks federico-stacks left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I noticed that some tests fail consistently on this PR (I re-ran them a bunch of times):

  • nakamoto_integrations::check_as_contract_rollback
  • nakamoto_integrations::check_block_time_keyword
  • nakamoto_integrations::check_restrict_assets_rollback
  • nakamoto_integrations:: check_with_stacking_allowances_delegate_stx
  • nakamoto_integrations:: check_with_stacking_allowances_stack_stx

They pass on main, so this doesn’t appear to be a bug introduced by this change. More likely, the change exposes an existing race condition in these tests. The failures occur in next_block_and_mine_commit (timeout). My guess is that, before the patch, rebuilding the route table for each HTTP connection made the /v2/info RPC slow enough for the relayer to submit its block commit first.

The epoch 3.3 boot loop in these tests only waits for a Stacks block, then
`next_block_and_mine_commit` immediately mines the next burn block. If the
relayer hasn't submitted its commit for the new tenure yet, that burn block
has an empty sortition, the miner extends its tenure without committing to a
newer tip, and the helper times out.

Faster RPC handling made this race lose consistently, so wait for a commit on
the current burn tip first, as `wait_for_tip_commit` does for neon tests.
@ccipherx

Copy link
Copy Markdown
Author

I noticed that some tests fail consistently on this PR (I re-ran them a bunch of times):

  • nakamoto_integrations::check_as_contract_rollback
  • nakamoto_integrations::check_block_time_keyword
  • nakamoto_integrations::check_restrict_assets_rollback
  • nakamoto_integrations:: check_with_stacking_allowances_delegate_stx
  • nakamoto_integrations:: check_with_stacking_allowances_stack_stx

They pass on main, so this doesn’t appear to be a bug introduced by this change. More likely, the change exposes an existing race condition in these tests. The failures occur in next_block_and_mine_commit (timeout). My guess is that, before the patch, rebuilding the route table for each HTTP connection made the /v2/info RPC slow enough for the relayer to submit its block commit first.

the race was on to mine the next burn block before the relayer’s commit arrived, fixed it by waiting for the naka tip commit before mining

Comment thread stacks-node/src/tests/nakamoto_integrations.rs

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StacksHttp rebuilds the entire RPC route table (~100 regex compilations, ~5 ms) on every inbound HTTP connection

4 participants