Replace libevent with our own HTTP and socket-handling implementation #35182

pull pinheadmz wants to merge 26 commits into bitcoin:master from pinheadmz:http-rewrite-30Apr2026 changing 18 files +2239 −602
  1. pinheadmz commented at 3:41 PM on April 30, 2026: member

    Continued from #32061.

    This is a major component of removing libevent as a dependency of the project, by replacing the HTTP server used for RPC and REST with one implemented entirely within the Bitcoin Core codebase. The new HTTPServer class runs its own I/O thread, handling socket connections with code based on #30988, but tailored specifically for HTTP.

    Commit strategy:

    • Isolate the existing libevent-based HTTP server in a namespace http_libevent
    • Implement HTTP in a new namespace http_bitcoin (classes like HTTPRequest, HTTPClient, etc...)
    • Switch bitcoind from the libevent server to the new server
    • Clean up (delete http_libevent)

    Fuzz testing

    libfuzzer

    fuzzamoto

    Integration testing:

    I am testing the new HTTP server by forking projects that integrate with bitcoin via HTTP and running their integration tests with bitcoind built from this branch (on Github actions). I will continue adding integrations over time, and re-running these CI tests as this branch gets rebased:

  2. DrahtBot commented at 3:42 PM on April 30, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK vasild
    Concept ACK rkrux

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34411 ([POC] Full Libevent removal by fanquake)
    • #34038 (logging: replace -loglevel with -trace, expose trace logging via RPC by ajtowns)
    • #26022 (Add util::ResultPtr class by ryanofsky)
    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
    • #25665 (refactor: Add util::Result failure types and ability to merge result values by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • //! Client has requested to keep the connection open after all requests have been responded to. / //! m_keep_alive=true can be overriden ... -> overridden [misspelled word; this is the only clear typo impacting comprehension]

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • Lookup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion", 0, false).value() in src/test/httpserver_tests.cpp
    • Lookup("0.0.0.0", 0, false).value() in src/test/httpserver_tests.cpp

    Possible places where comparison-specific test macros should replace generic comparisons:

    • [test/functional/interface_http.py] assert response.status == http.client.UNAUTHORIZED -> use assert_equal(response.status, http.client.UNAUTHORIZED)
    • [test/functional/interface_http.py] assert response.status == http.client.METHOD_NOT_ALLOWED -> use assert_equal(response.status, http.client.METHOD_NOT_ALLOWED)
    • [test/functional/interface_http.py] assert response.status == http.client.NOT_FOUND -> use assert_equal(response.status, http.client.NOT_FOUND)

    <sup>2026-04-30 20:15:45</sup>

  3. fanquake added the label RPC/REST/ZMQ on Apr 30, 2026
  4. in src/httpserver.h:65 in e728c71f5a
      92 | -/** Return evhttp event base. This can be used by submodules to
      93 | - * queue timers or custom events.
      94 | - */
      95 | -struct event_base* EventBase();
      96 | +//! Maximum size of an HTTP request body (32 MB).
      97 | +constexpr uint64_t MAX_BODY_SIZE{0x02000000};
    


    vasild commented at 5:01 PM on April 30, 2026:
    //! Maximum size of an HTTP request body.
    constexpr uint64_t MAX_BODY_SIZE{32_MiB};
    

    (and include <util/byte_units.h>)


    pinheadmz commented at 5:29 PM on April 30, 2026:

    cool, using util/byte_units here

  5. in src/httpserver.cpp:417 in e728c71f5a
     497 | +            }
     498 | +
     499 | +            const auto chunk_size{ToIntegral<uint64_t>(util::TrimStringView(chunk_size_noext), /*base=*/16)};
     500 | +            if (!chunk_size) throw std::runtime_error("Cannot parse chunk length value");
     501 | +
     502 | +            if (*chunk_size > MAX_SIZE - m_body.size()) throw ContentTooLargeError("Chunk will exceed max body size");
    


    vasild commented at 5:08 PM on April 30, 2026:

    I think this is the case, but it is not immediately obvious that MAX_SIZE is greater or equal to m_body.size(). We do not want MAX_SIZE - m_body.size() to turn negative. It wouldn't hurt to add an extra check for this. if (MAX_SIZE < m_body.size()) { throw ..., or assert().


    pinheadmz commented at 5:30 PM on April 30, 2026:

    Adding the underflow check here. Also switched to MAX_BODY_SIZE which shouldve been in last rebase

  6. in src/httpserver.cpp:436 in e728c71f5a
     516 | +                // See https://httpwg.org/specs/rfc9112.html#rfc.section.7.1.2
     517 | +                const size_t trailer_start{reader.Consumed()};
     518 | +                while (true) {
     519 | +                    auto maybe_trailer = reader.ReadLine();
     520 | +                    if (reader.Consumed() - trailer_start > MAX_HEADERS_SIZE) {
     521 | +                      throw std::runtime_error("HTTP chunked trailer exceeds size limit");
    


    vasild commented at 5:10 PM on April 30, 2026:

    2 more spaces in the indentation of throw.

                        if (reader.Consumed() - trailer_start > MAX_HEADERS_SIZE) {
                            throw std::runtime_error("HTTP chunked trailer exceeds size limit");
    

    pinheadmz commented at 5:36 PM on April 30, 2026:

    👍

  7. vasild approved
  8. vasild commented at 5:14 PM on April 30, 2026: contributor

    ACK e728c71f5adafdcbc4b6a29d154ea5705003ba60

    <details> <summary>Show Signature</summary>

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    ACK e728c71f5adafdcbc4b6a29d154ea5705003ba60
    -----BEGIN PGP SIGNATURE-----
    
    iQQzBAEBCAAdFiEE5k2NRWFNsHVF2czBVN8G9ktVy78FAmnzjdkACgkQVN8G9ktV
    y7/UOSAAgi7UxBy3GrlhfUNH2hLetiZDtswp6EWjSgcRj3GjYUdKjhikRaED4zqc
    bOSh942CdU0LFTm14PqZ4bhybqjfMe8mZmvSoBWx+kGJKyiNOWM0P4qDzscKq5IC
    adAvv6A9SxzfnFSskf1aM97X3WeFgTBeT4S2ycZ6uurdqCCbfwtMtGgj0wqBw+Na
    r8p17RjJUiz78FNPaQ5Y0GDXsKhiTbs1OXZ8Tn2qtviy1QjAyDZLxo9tR768Cf8d
    32VcOMRoxBQ70fCtds5Zne+CjlGlAqw4P5tCKd337L2/3wO+uxwIAlAQHu1MM41f
    itFV8+d34lM7VdsyWTNYYgc0dDqgvUroNZdTHwGJJzEni3RbTYv4H6bW13Av76ci
    ncUOYJFTW+XexMLKvl8QZaWNASKZGPHtp9jciOvEtORRIdPDmEJxgLpQEYT3ITES
    oI5uLCd9uGcUK/zOvIdfUDMdL30fiB0YCcRU9kW3DebdYAWdBdovB/S0NP0VP0J2
    F18MHXGybUektYyqYavjuRzBE+lmfZlHW3cblUvbVTzU6/BeCiJHDDaDiRYcV3ba
    qz6D+JU67f+Q8HxDk/oLXfHRQYODn847USgaFuc9TqK495+jEyBfSNmGWGZPVMAX
    GA6hTPmVq5z6pqoKTbvF1v4aCv4k6n+2EJBH1o4N7Q+fHxNVlzOsff/yCiZ88oaw
    W1aM2Co2pJ2YESxddUR1QnpbP9JFMRAlJYzGkDfMjD8rLKZppQm2v4fnu0R5xZxX
    Q6bBSeRhzBs+QLs180rzluUNJ8laIAzAmuJe4zOH6MPT/Js7siogj1NYQK4/t123
    3/QQiSFWJ1hHbg8D6M5wAaBjpEoxzJ825xjMc6zud7UjHlslBYPsWbYxvz2gDZ8r
    5gbsqoprGOteHvc0eyuDf7yxLoSdqUYtl9EXwZmLt20edBcbDHW49hGd6JsTCZk8
    MNGOISi4MiXkPjOa/XBil3IN25YuB4rAr4Bx71D/ptgl8JjuVGU6HIYpoySMj89D
    uWR/IV15UcPXQBAhJsh7ZCOsY9MzfQ8QLskiLsX9Mfpe6/MOfi1N3XFWjozehPkq
    YzQ/82BK8HwmMJ/hXIppJhlwWncHvnQrtZ1NSsR8Bs0ca86GZKHR6s7LFFupej40
    OWEI6cNzSk2hUosNgttmcHdjznIwxNcDF7wfVDW1yc7h9XEUUgiMzU69E53J6Hgk
    GTW92NM/kw5kvY54ghehJDvNLY8Pq1PblIOZmXakMYrWpDxYlu2l0zN+sYZ7sdkV
    JfU7yWtymLHrg0TdIPuTzuhTrvpQTksYO/vo3hyJS/5XqrIWbyEgc+LtZvnIl8wY
    Cf6tnL9dxbIWtp0UoqzGrooYBUvXlA==
    =2kAW
    -----END PGP SIGNATURE-----
    

    vasild's public key is on openpgp.org

    </details>

  9. pinheadmz force-pushed on Apr 30, 2026
  10. pinheadmz commented at 7:51 PM on April 30, 2026: member

    push to 0a4929c0bf:

    Address feedback from @vasild

  11. test: cover common HTTP attacks and common malformed requests 53b740832c
  12. http: enclose libevent-dependent code in a namespace
    This commit is a no-op to isolate HTTP methods and objects that
    depend on libevent. Following commits will add replacement objects
    and methods in a new namespace for testing and review before
    switching over the server.
    784a997723
  13. http: Implement HTTPHeaders class
    see:
    https://www.rfc-editor.org/rfc/rfc2616#section-4.2
    https://www.rfc-editor.org/rfc/rfc7231#section-5
    https://www.rfc-editor.org/rfc/rfc7231#section-7
    https://httpwg.org/specs/rfc9111.html#header.field.definitions
    83e493e8cb
  14. http: Implement HTTPResponse class
    HTTP Response message:
    https://datatracker.ietf.org/doc/html/rfc1945#section-6
    
    Status line (first line of response):
    https://datatracker.ietf.org/doc/html/rfc1945#section-6.1
    
    Status code definitions:
    https://datatracker.ietf.org/doc/html/rfc1945#section-9
    b7b2845816
  15. http: Implement HTTPRequest class
    HTTP Request message:
    https://datatracker.ietf.org/doc/html/rfc1945#section-5
    
    Request Line aka Control Line aka first line:
    https://datatracker.ietf.org/doc/html/rfc1945#section-5.1
    
    See message_read_status() in libevent http.c for how
    `MORE_DATA_EXPECTED` is handled there
    7a729ac8c6
  16. http: Introduce HTTPServer class and implement binding to listening socket
    Introduce a new low-level socket managing class `HTTPServer`.
    
    BindAndStartListening() was copied from CConnMan's BindListenPort()
    in net.cpp and modernized.
    
    Unit-test it with a new class `SocketTestingSetup` which mocks
    `CreateSock()` and will enable mock client I/O in future commits.
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    10635b629e
  17. HTTPServer: implement and test AcceptConnection()
    AcceptConnection() is mostly copied from CConmann in net.cpp
    and then modernized.
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    64e08772f0
  18. HTTPServer: generate sequential Ids for each newly accepted connection
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    5d0090d986
  19. http: Introduce HTTPClient class 670707d32a
  20. HTTPServer: start an I/O loop in a new thread and accept connections
    Socket handling methods are copied from CConnMan:
    
    `CConnman::GenerateWaitSockets()`
    `CConnman::SocketHandlerListening()`
    `CConnman::ThreadSocketHandler()` and `CConnman::SocketHandler()` are combined into ThreadSocketHandler()`.
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    72d93c295d
  21. HTTPServer: read requests from connected clients
    `SocketHandlerConnected()` adapted from CConnman
    
    Testing this requires adding a new feature to the SocketTestingSetup,
    inserting a "request" payload into the mock client that connects
    to us.
    
    This commit also moves IOErrorIsPermanent() from sock.cpp to sock.h
    so it can be called from the socket handler in httpserver.cpp
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    7da1a3dac0
  22. HTTPserver: support "chunked" Transfer-Encoding 69bb74f10c
  23. HTTPServer: compose and send replies to connected clients
    Sockets-touching bits copied and adapted from `CConnman::SocketSendData()`
    
    Testing this requires adding a new feature to the SocketTestingSetup,
    returning the DynSock I/O pipes from the mock socket so the received
    data can be checked.
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    fc55c0ba7a
  24. HTTPServer: disconnect clients d4dad93560
  25. Allow http workers to send data optimistically as an optimization 0ae2d4713f
  26. HTTPServer: use a queue to pipeline requests from each connected client
    See https://www.rfc-editor.org/rfc/rfc7230#section-6.3.2
    
    > A server MAY process a sequence of pipelined requests in
      parallel if they all have safe methods (Section 4.2.1 of [RFC7231]),
      but it MUST send the corresponding responses in the same order that
      the requests were received.
    
    We choose NOT to process requests in parallel. They are executed in
    the order recevied as well as responded to in the order received.
    This prevents race conditions where old state may get sent in response
    to requests that are very quick to process but were requested later on
    in the queue.
    1b6c7ba827
  27. define HTTP request methods at module level outside of class
    This is a refactor to prepare for matching the API of HTTPRequest
    definitions in both namespaces http_bitcoin and http_libevent. In
    particular, to provide a consistent return type for GetRequestMethod()
    in both classes.
    ad7e7d5638
  28. Add helper methods to HTTPRequest to match original API
    These methods are called by http_request_cb() and are present in the
    original http_libevent::HTTPRequest.
    c335c1a0f9
  29. refactor: split http_request_cb into libevent callback and dispatch
    The original function is passed to libevent as a callback when HTTP
    requests are received and processed. It wrapped the libevent request
    object in a http_libevent::HTTPRequest and then handed that off to
    bitcoin for basic checks and finally dispatch to worker threads.
    
    In this commit we split the function after the
    http_libevent::HTTPRequest is created, and pass that object to a new
    function that maintains the logic of checking and dispatching.
    
    This will be the merge point for http_libevent and http_bitcoin,
    where HTTPRequest objects from either namespace have the same
    downstream lifecycle.
    ae842def6c
  30. refactor: split HTTPBindAddresses into config parse and libevent setup
    The original function was already naturally split into two chunks:
    First, we parse and validate the users' RPC configuration for IPs and
    ports. Next we bind libevent's http server to the appropriate
    endpoints.
    
    This commit splits these chunks into two separate functions, leaving
    the argument parsing in the common space of the module and moving the
    libevent-specific binding into the http_libevent namespace.
    
    A future commit will implement http_bitcoin::HTTPBindAddresses to
    bind the validate list of endpoints by the new HTTP server.
    7dad8a47ac
  31. HTTPServer: implement control methods to match legacy API 6d6e5751f3
  32. HTTPServer: disconnect after idle timeout (-rpcservertimeout) c1ea64575a
  33. http: switch servers from libevent to bitcoin fda243779e
  34. fuzz: switch http_libevent::HTTPRequest to http_bitcoin::HTTPRequest 70a95b8989
  35. http: remove libevent usage from this subsystem 0683367a67
  36. logging: remove libevent category 5952e48ff4
  37. pinheadmz force-pushed on Apr 30, 2026
  38. pinheadmz commented at 8:15 PM on April 30, 2026: member

    push to 5952e48ff4:

    rebase on master to fix CI (see #35186)

  39. in src/httpserver.cpp:310 in 0a4929c0bf
     360 | +        // https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
     361 | +        const size_t pos{line.find(':')};
     362 | +        if (pos == std::string::npos) throw std::runtime_error("HTTP header missing colon (:)");
     363 | +
     364 | +        // Whitespace is optional
     365 | +        std::string key = util::TrimString(std::string_view(line).substr(0, pos));
    


    janb84 commented at 8:15 PM on April 30, 2026:
            // RFC 9110 Section 5.6.2: no whitespace is allowed between the field name and colon.
            if (pos > 0 && (line[pos - 1] == ' ' || line[pos - 1] == '\t')) {
                throw std::runtime_error("HTTP header has whitespace before colon (:)");
            }
    
            // optional whitespace is allowed around the field value but not within the field name.
            std::string key{std::string_view(line).substr(0, pos)};
    

    NIT: current code allows optional whitespace in a way that is niet compliant with RFC 9112 Section 5.1. This (as per RFC) MUST return a 400, the code currently returns a 405

  40. in src/test/httpserver_tests.cpp:161 in 0a4929c0bf
     165 | +    {
     166 | +        // contains NUL
     167 | +        util::LineReader reader{std::string_view{"X-Custom: foo\0bar\n", 18}, /*max_line_length=*/MAX_HEADERS_SIZE};
     168 | +        BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Invalid header contains NUL"});
     169 | +    }
     170 | +    {
    


    janb84 commented at 8:15 PM on April 30, 2026:
        {
            // RFC 9110 Section 5.6.2: no whitespace allowed between field name and colon
            util::LineReader reader{"Host : value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
            BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header has whitespace before colon (:)"});
        }
        {
            // Tab before colon is also rejected
            util::LineReader reader{"Host\t: value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
            BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header has whitespace before colon (:)"});
        }
        {
    

    Extra test for the RFC 9110 no whitespace allowed between field name and colon rule.


    pinheadmz commented at 8:18 PM on April 30, 2026:

    How does this test play on master? See #32061#pullrequestreview-4172127857 if this is the same issue, I think libevent is also lenient about whitesspace in headers


    janb84 commented at 8:44 PM on April 30, 2026:

    master fails also. so lets park this for an followup.

  41. DrahtBot added the label CI failed on Apr 30, 2026
  42. DrahtBot commented at 8:16 PM on April 30, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/25186108957/job/73843745290</sub> <sub>LLM reason (✨ experimental): CI failed because IWYU reported missing/incorrect includes (auto-fix required) and the job was forced to fail (Failure generated from IWYU).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  43. DrahtBot removed the label CI failed on Apr 30, 2026
  44. rkrux commented at 7:32 AM on May 1, 2026: contributor

    Concept ACK 5952e48ff4490e3c0607bd34284112bcdbdeee05, will review

    Can update the developer notes as well in this PR by replacing the libevent occurrences with the new one here:

    https://github.com/bitcoin/bitcoin/blob/404470505a8b574b5e4b0e1ffc481ed3fc8caf77/doc/developer-notes.md?plain=1#L686-L700

  45. vasild approved
  46. vasild commented at 8:56 AM on May 1, 2026: contributor

    ACK 5952e48ff4490e3c0607bd34284112bcdbdeee05

    <details> <summary>Show Signature</summary>

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    ACK 5952e48ff4490e3c0607bd34284112bcdbdeee05
    -----BEGIN PGP SIGNATURE-----
    
    iQQzBAEBCAAdFiEE5k2NRWFNsHVF2czBVN8G9ktVy78FAmn0arQACgkQVN8G9ktV
    y7+8Lx/9G3dv9qVk1OA65C7SeNOOMc/jE2gXqkmhSQ6M9SKzrsUjBAetZb5vH7Q1
    XiVOc7WKC2UKXWm7kvl0+LoBNpI2T33KkAE+WUsD82AgFHIlt+vWWlqCDBRbrrVq
    iKOPBcr4WJL7vvvs/x86+w8J8kGvZVATDijujqm5k3SP5FimJ6eGSw4cStrp0yrb
    Jm0ZXAwT5Aplg/7w496KxvR8W1+n9af8RX8aoWl3Ooc6XSd+tlFrM8sSDxCumekB
    QQmW5PdBlr9n40kMXJd42ItcbV35fZSlIYafH9Zgj3N9M4W3aV5w4CiVVr9sd0tR
    oH2KTSV8L+S8RnM/O6v3DQmj9vL0cPxYl2gV5P25wQozz3KzOdMS7wRnYPEg5+qj
    UQPr7cSMf31PzolScHbGsxjZNYpvHy5Db3IJ0coG6wC2Cv1ARV2K0XF5JhyvtmYS
    3KWOMtelQ52rYzmHgG1QDDYn9LL8rcEQbHGeBRunwQiiutG/VMAMnfPAeUamNj3Y
    QsNWV3NQwTfbQCM8P8Pwso5k5k53sh0eOGGofsPh2q7fLwS1WIRjzikAOVjPDThJ
    JF57PPcgC3Zq0YmDKWyHa7fi7WiYDAzYVE+Z8ML65efhFQpQCoE4dvZalj/jCDWV
    /CfSTyuEAAsJJpDqLV5NO6bJcxbYPdcb1KmMrgMjKkNNUw8RPP8nYucjOHTHu8ku
    CT9y23+uB6p5uphVaRk6Ay8/yolucaHWx/ykBp0gldU2bb/PO2sTvl/pWJ6mAl+g
    +t5Ei89CvbdzjvOIbHOEP7Oiim52ZPuYQ9owr85v1tCkdZHBRJYf1BuxI2RefMAo
    EikQk35kDqk0fW3+uY4OTiG21bnvbK1R/ISXtmrX1tbmwBCAuyr3CfXv0dxqsKqM
    BQW5vf/5vMAfYCepzB0xBrAL+nb71m5IIiTOtuTZLFboNdt6klOv9W8ksJvjvtNg
    4crJwWsL8zx9xdCgoashHQ55kuNnKfaCXt6Z8DGLxtfQKRiPFW+06ZVaq9dkhBmO
    vjmqDej2Tiic9bWIDgUhwca+FP4KRRwx7bEB6P4tcA/GDRRfr6RiKWiMjCmvqNwS
    jF/vaXMa7kHp/bbF2qJVWtKg14kJMBlsEDMdfXtgccs38lDehNqZRQHHVp/fyKth
    IIp5tRjqsVInakGkpuDvzEQxYxOgtxrc7/7L7ZBKHC/28dZL1JtL92BlHeO0g3mb
    QlaCAyJnXcg5lhN03VtjKuGFrRQ7mRS1DfUucN6D7LaqP2k4P4gqM+QjwA4DnfiI
    d9OecGJleocumnbFfaMsWOgKnS1T2TFhojxnGxCY31+HpnR0Kzb7D7n1srEHOH1t
    e9YQXXclQIn+SwIlERiph5QNPimXCA==
    =xzuk
    -----END PGP SIGNATURE-----
    

    vasild's public key is on openpgp.org

    </details>

  47. DrahtBot requested review from rkrux on May 1, 2026
  48. b-l-u-e commented at 12:18 PM on May 1, 2026: contributor

    i noticed a behavior that PUT is being treated as a known HTTPRequestMethod but the HTTP dispatch layer only rejects UNKNOWN methods. so PUT / is allowed through the registered path handler and is later rejected by JSON-RPC rather than being rejected at the HTTP layer like other unsupported methods.

    i tested the behavior:

     curl -i --user user:pass -X PUT \
      -H 'Content-Type: application/json' \
      --data '{"method":"getblockcount","params":[]}' \
      http://127.0.0.1:18443/
    echo
    HTTP/1.1 405 Method Not Allowed
    Date: Fri, 01 May 2026 12:19:35 GMT
    Content-Length: 41
    Content-Type: text/html; charset=ISO-8859-1
    
    JSONRPC server handles only POST requests
    
  49. pinheadmz commented at 2:48 PM on May 1, 2026: member

    @b-l-u-e

    PUT is being treated as a known HTTPRequestMethod

    I think this is correct, and on master with libevent your curl command has the same result. In the future bitcoin might support an HTTP API with PUT so I don't think it needs to be rejected here. Also note there is a functional test in this PR that covers the more suspicious methods:

        def check_disallowed_http_methods(self):
            self.log.info("Check that unsafe or unsupported HTTP methods are rejected")
            for method in ['TRACE', 'CONNECT', 'DELETE', 'PATCH', 'OPTIONS']:
    
  50. b-l-u-e commented at 3:39 PM on May 1, 2026: contributor

    I think this is correct, and on master with libevent your curl command has the same result. In the future bitcoin might support an HTTP API with PUT so I don't think it needs to be rejected here. Also note there is a functional test in this PR that covers the more suspicious methods:

        def check_disallowed_http_methods(self):
            self.log.info("Check that unsafe or unsupported HTTP methods are rejected")
            for method in ['TRACE', 'CONNECT', 'DELETE', 'PATCH', 'OPTIONS']:
    

    ahh yes it was an existing behavior confirmed with libevent and yeah these methods carries more risk as well thanks for clarificaton

  51. b-l-u-e commented at 7:59 PM on May 1, 2026: contributor

    i think theres data race on m_request_dispatcher during shutdown when SetRequestHandler is called from the main thread during InterruptHTTPServer() while the I/O thread is still inside MaybeDispatchRequestsFromClient invoking the same function

    heres main thread during shutdown

    diff --git a/src/httpserver.h b/src/httpserver.h
    +++ b/src/httpserver.h
    @@ -0,0 +238,1 @@
    +    void SetRequestHandler(std::function<void(std::unique_ptr<HTTPRequest>&&)> func) { m_request_dispatcher = func; }
    
    

    here were its called from

    +++ b/src/httpserver.cpp
    @@ -0,0 +1195,7 @@
    +void InterruptHTTPServer()
    +{
    +    LogDebug(BCLog::HTTP, "Interrupting HTTP server");
    +    if (g_http_server) {
    +        // Reject all new requests
    +        g_http_server->SetRequestHandler(RejectRequest);
    +    }
    

    HTTP I/O thread

    +++ b/src/httpserver.cpp
    @@ -0,0 +977,5 @@
    +    if (!client->m_req_queue.empty()) {
    +        client->m_req_busy = true;
    +        m_request_dispatcher(std::move(client->m_req_queue.front()));
    +        client->m_req_queue.pop_front();
    +    }
    

    called from I/O thread started by StartSocketsThreads()

    +++ b/src/httpserver.cpp
    @@ -0,0 +709,5 @@
    +void HTTPServer::StartSocketsThreads()
    +{
    +    m_thread_socket_handler = std::thread(&util::TraceThread,
    +                                          "http",
    +                                          [this] { ThreadSocketHandler(); });
    

    <details> <summary>output</summary>

    build-tsan/tsan.log.115918
    2:WARNING: ThreadSanitizer: data race (pid=115918)
    6:    [#2](/bitcoin-bitcoin/2/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e581b)
    26:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c1c6)
    100:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/std_function.h:247 in std::_Function_base::_M_empty() const
    103:WARNING: ThreadSanitizer: data race (pid=115918)
    106:    [#1](/bitcoin-bitcoin/1/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e583c)
    126:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c213)
    200:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/std_function.h:591 in std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>::operator()(std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&) const
    203:WARNING: ThreadSanitizer: data race (pid=115918)
    209:    [#4](/bitcoin-bitcoin/4/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e5849)
    229:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c17d)
    303:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/invoke.h:60 in __invoke_impl<void, httpserver_tests::race_set_request_handler_during_dispatch::test_method()::<lambda(std::unique_ptr<http_bitcoin::HTTPRequest>&&)>&, std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> > >
    
    build-tsan/tsan.log.45095
    2:WARNING: ThreadSanitizer: data race (pid=45095)
    6:    [#2](/bitcoin-bitcoin/2/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e581b)
    26:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c385)
    100:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/std_function.h:247 in std::_Function_base::_M_empty() const
    103:WARNING: ThreadSanitizer: data race (pid=45095)
    106:    [#1](/bitcoin-bitcoin/1/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e583c)
    126:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c213)
    200:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/std_function.h:591 in std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>::operator()(std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&) const
    203:WARNING: ThreadSanitizer: data race (pid=45095)
    209:    [#4](/bitcoin-bitcoin/4/) http_bitcoin::HTTPServer::MaybeDispatchRequestsFromClient(std::shared_ptr<http_bitcoin::HTTPClient> const&) const /home/user/projects/bitcoin/src/httpserver.cpp:979 (test_bitcoin+0x13e5849)
    229:    [#3](/bitcoin-bitcoin/3/) http_bitcoin::HTTPServer::SetRequestHandler(std::function<void (std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> >&&)>) /home/user/projects/bitcoin/src/httpserver.h:238 (test_bitcoin+0x90c17d)
    303:SUMMARY: ThreadSanitizer: data race /usr/include/c++/13/bits/invoke.h:60 in __invoke_impl<void, httpserver_tests::race_set_request_handler_during_dispatch::test_method()::<lambda(std::unique_ptr<http_bitcoin::HTTPRequest>&&)>&, std::unique_ptr<http_bitcoin::HTTPRequest, std::default_delete<http_bitcoin::HTTPRequest> > >
    

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-02 21:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me