test: Check that RPCs do not time out, even under load #34927

pull maflcko wants to merge 2 commits into bitcoin:master from maflcko:2603-test-rpc-sla changing 4 files +61 −4
  1. maflcko commented at 4:35 PM on March 26, 2026: member

    It turns out there is no test currently to check that the RPC server does not time out under load. With "load" I mean a flood of trivial payloads. That is, the only work needed is JSON encoding and decoding of (let's say) a block of data of 2 MB or so. This may take a few milliseconds, but should never take more than a few seconds.

    So add a test for this.

  2. DrahtBot renamed this:
    test: Check that RPCs do not time out, even under load
    test: Check that RPCs do not time out, even under load
    on Mar 26, 2026
  3. DrahtBot added the label Tests on Mar 26, 2026
  4. maflcko marked this as a draft on Mar 26, 2026
  5. DrahtBot commented at 4:35 PM on March 26, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34927.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK enirox001, sedited
    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:

    • #34794 (rest: add Cache-Control headers to REST responses by w0xlt)

    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-->

  6. maflcko commented at 4:36 PM on March 26, 2026: member

    Draft for now, because the test fails :(

    Edit: It fails in CI, but it passes locally for me:

    ./bld-cmake/test/functional/test_runner.py -j 16 $( printf 'rpc_echo_payload.py %.0s' {1..2000} )
    
  7. DrahtBot added the label CI failed on Mar 26, 2026
  8. maflcko commented at 5:30 PM on March 26, 2026: member

    Timed out after 40 minutes in https://github.com/bitcoin/bitcoin/actions/runs/23606118115/job/68749250793?pr=34927#step:11:3195:

    2026-03-26T16:46:08.3589405Z Remaining jobs: [interface_ipc_mining.py, rpc_echo_payload.py]
    2026-03-26T16:47:46.9801923Z ......................................................................................................................................................................................................
    2026-03-26T16:47:47.4805641Z                                                                                                                                                                                                       
    2026-03-26T16:47:47.4806053Z Remaining jobs: [rpc_echo_payload.py]
    2026-03-26T17:27:36.9551875Z ##[error]The operation was canceled.
    
  9. maflcko commented at 5:30 PM on March 26, 2026: member

    cc @dergoegge Can you copy-paste the Python test into Antithesis to see if it fails there?

  10. maflcko marked this as ready for review on Apr 1, 2026
  11. maflcko renamed this:
    test: Check that RPCs do not time out, even under load
    test: Check that RPCs do not time out, even under load. Disable Nagle's
    on Apr 1, 2026
  12. maflcko force-pushed on Apr 1, 2026
  13. rkrux commented at 5:55 PM on April 1, 2026: contributor

    Can this be tested with 120 commits or so?

  14. maflcko force-pushed on Apr 1, 2026
  15. maflcko renamed this:
    test: Check that RPCs do not time out, even under load. Disable Nagle's
    test: Check that RPCs do not time out, even under load
    on Apr 1, 2026
  16. maflcko commented at 7:05 PM on April 1, 2026: member

    Can this be tested with 120 commits or so?

    Sorry, turns out I was wrong, and I spun up a new connection while disabling Nagle's. Obviously spinning up a new connection is already known to work around the issue and Nagle's didn't seem to affect it.

  17. maflcko added the label DrahtBot Guix build requested on Apr 4, 2026
  18. DrahtBot commented at 10:57 AM on April 7, 2026: contributor

    <!--9cd9c72976c961c55c7acef8f6ba82cd-->

    Guix builds (on x86_64) [untrusted test-only build, possibly unsafe, not for production use]

    File commit a7c30da1f6fc2eebd7514dd032fbb54940d467de<br>(master) commit ae63d766485540b3e851b46eea5aae5c9fdb73f2<br>(pull/34927/merge)
    *-aarch64-linux-gnu-debug.tar.gz 5129654fd42dfcc8... 96c3eea00e0cf535...
    *-aarch64-linux-gnu.tar.gz e0e9b6d39f3ea4ce... 5064aeba572e280b...
    *-arm-linux-gnueabihf-debug.tar.gz c11bf60c9ccf3c53... 3214e6b9250d7d3e...
    *-arm-linux-gnueabihf.tar.gz 1b9a940303194a20... aa9b3430355e5829...
    *-arm64-apple-darwin-codesigning.tar.gz 4159e3bfa00cea0b... c0157a52ea38e3d2...
    *-arm64-apple-darwin-unsigned.tar.gz d5d24298bcd3b88e... 7eac94e3177a0088...
    *-arm64-apple-darwin-unsigned.zip 3bff56e0b3c034b8... f290aa5359c3b1d5...
    *-powerpc64-linux-gnu-debug.tar.gz b3b7a130b6ef0e11... 657581e5ea1e1375...
    *-powerpc64-linux-gnu.tar.gz 46b7eccc4b1f9daa... c50504f4191f64c6...
    *-riscv64-linux-gnu-debug.tar.gz d45c1c48f26a22e4... 28b3d2810f4db830...
    *-riscv64-linux-gnu.tar.gz 463b0c63c43fc134... c73ab6d78882465f...
    *-win64-codesigning.tar.gz 9e4f0096eea9f2e4... 8dbaa347fa55ab79...
    *-win64-debug.zip 3ad9b746fdb80f9d... 69844041afe3ce64...
    *-win64-setup-unsigned.exe 5f60da153f966f6e... e972e10fa59ff735...
    *-win64-unsigned.zip 4a4c25d84476aa9f... bbfe2a9f09c5d9d2...
    *-x86_64-apple-darwin-codesigning.tar.gz dd3d28797694f14c... 5fa79104ae9a816d...
    *-x86_64-apple-darwin-unsigned.tar.gz ed0ab5e432488d86... b8de0cc03d8267d2...
    *-x86_64-apple-darwin-unsigned.zip 3cf6ca00b8c0cf88... 5860a84107524382...
    *-x86_64-linux-gnu-debug.tar.gz 3ca9cd40187259af... bbd5956ca60702bf...
    *-x86_64-linux-gnu.tar.gz ff514a472203e8cb... 58212f449aaf88ef...
    *.tar.gz 3e616d7febbce966... 1d89f5d3a55c020e...
    SHA256SUMS.part db9bf1d2856949a7... 106e86f2786833da...
    guix_build.log dcda2d8624c32cbb... f7426bafa50d9297...
    guix_build.log.diff bf28c8f2f9efe743...
  19. DrahtBot removed the label DrahtBot Guix build requested on Apr 7, 2026
  20. maflcko force-pushed on Apr 20, 2026
  21. maflcko force-pushed on Apr 21, 2026
  22. enirox001 commented at 2:33 PM on April 21, 2026: contributor

    Concept ACK

  23. maflcko marked this as a draft on Apr 21, 2026
  24. maflcko commented at 2:38 PM on April 21, 2026: member

    Turning into draft while CI is red, but the code should be correct, reviewable, and mergeable after libevent is nuked.

  25. maflcko force-pushed on May 12, 2026
  26. DrahtBot removed the label CI failed on May 12, 2026
  27. DrahtBot added the label CI failed on May 12, 2026
  28. DrahtBot added the label Needs rebase on May 22, 2026
  29. maflcko force-pushed on May 22, 2026
  30. DrahtBot removed the label Needs rebase on May 22, 2026
  31. maflcko marked this as ready for review on Jun 27, 2026
  32. maflcko force-pushed on Jun 27, 2026
  33. maflcko commented at 1:00 PM on June 27, 2026: member

    Rebased and taken out of draft. CI should be passing now.

  34. maflcko force-pushed on Jun 27, 2026
  35. maflcko force-pushed on Jun 27, 2026
  36. DrahtBot removed the label CI failed on Jun 27, 2026
  37. sedited approved
  38. sedited commented at 10:55 AM on July 11, 2026: contributor

    ACK fa04bad48b2e825b28a0a9f0300ff235409cc684

  39. sedited commented at 11:26 AM on August 5, 2026: contributor

    @enirox001 do you want to take another look here?

  40. in test/functional/test_framework/test_node.py:955 in fac378d434
     950 | +            if match:
     951 | +                message = match.group(1)
     952 | +                raise JSONRPCException(dict(
     953 | +                    code=-342,
     954 | +                    message=f"non-JSON HTTP response with '503 Service Unavailable' from server: {message}",
     955 | +                ))
    


    enirox001 commented at 9:26 AM on August 6, 2026:

    In https://github.com/bitcoin/bitcoin/pull/34927/changes/fac378d434b7947ca79b068206b0d0fcc20885cd test: Map cli CalledProcessError on server error to JSONRPCException

    Would it be worth passing http_status=503 when constructing this JSONRPCException, so callers retain the HTTP status in addition to the RPC error code and message?

    JSONRPCException has this constructor def __init__(self, rpc_error, http_status=None): so i think this can be used as

    index cfd841c92a..f2456589ff 100755
    --- a/test/functional/test_framework/test_node.py
    +++ b/test/functional/test_framework/test_node.py
    @@ -952,7 +952,7 @@ class TestNodeCLI():
                     raise JSONRPCException(dict(
                         code=-342,
                         message=f"non-JSON HTTP response with '503 Service Unavailable' from server: {message}",
    -                ))
    +                ), http_status=503)
                 # Ignore cli_stdout, raise with cli_stderr
                 raise subprocess.CalledProcessError(returncode, p_args, output=cli_stderr)
             try:
    

    maflcko commented at 11:37 AM on August 6, 2026:

    thx, done

  41. in test/functional/rpc_echo_payload.py:13 in fa04bad48b
       8 | +from test_framework.test_framework import BitcoinTestFramework
       9 | +from test_framework.util import assert_equal, JSONRPCException
      10 | +import random
      11 | +
      12 | +
      13 | +class RpcSlaTest(BitcoinTestFramework):
    


    enirox001 commented at 9:30 AM on August 6, 2026:

    In https://github.com/bitcoin/bitcoin/pull/34927/changes/fa04bad48b2e825b28a0a9f0300ff235409cc684 test: Check that RPCs do not time out, even under load

    I do not understand what this Sla means here, Could this be named RPCPayloadTest It is difficult to infer from the class name which behavior the test covers


    maflcko commented at 11:37 AM on August 6, 2026:

    SLA is a contract about the service level. Happy to pick any other name. Maybe RPCEchoPayloadTest?


    enirox001 commented at 12:08 PM on August 6, 2026:

    Thanks, RPCEchoPayloadTest is clearer to me.

  42. in test/functional/rpc_echo_payload.py:28 in fa04bad48b outdated
      23 | +        data = random.randbytes(1_999_000).hex()
      24 | +
      25 | +        def check_results(rpc):
      26 | +            self.log.info("Starting thread ...")
      27 | +            for i in range(200):
      28 | +                payload = data[: random.randrange(0, len(data))]
    


    enirox001 commented at 10:14 AM on August 6, 2026:

    In https://github.com/bitcoin/bitcoin/pull/34927/changes/fa04bad48b2e825b28a0a9f0300ff235409cc684#r3727565203 test: Check that RPCs do not time out, even under load

    My understanding is that random data is not necessarily the same as invalid transaction data. Most random payloads will not decode, but occasionally the bytes can have the structure of a transaction such as a version, input count, inputs, outputs, and locktime. In that case, decoding succeeds, and it moves on to validation

    I checked this by temporarily submitting the following syntactically valid serialized transaction

    valid_tx = "020000000101010101010101010101010101010101010101010101010101010101010101010000000000ffffffff010000000000000000016a00000000"
    
    assert_raises_rpc_error( -26,  "tx-size-small", node.sendrawtransaction,  valid_tx,)
    

    This returned tx-size-small rather than tx decode failed', indicating that the payload was successfully decoded and reached transaction validation. If the randomized test selects such a payload, its existing error message check will fail even though the RPC behaved correctly.

    I am not sure how likely this is in practice, and it may be very unlikely. However, the test makes hundreds of random selections per run and may be executed many times across CI. Since the test depends on the payload being undecodable, it seems better to guarantee that condition rather than rely on random data being invalid.

    Could we guarantee that the payload is undecodable by appending a non-hex character?

    rpc.sendrawtransaction(payload + "x")

    Since x is not hexadecimal, the payload cannot be decoded as a serialized transaction and must take the expected decode-error path.


    maflcko commented at 11:37 AM on August 6, 2026:

    thx, done, but for the first char.


    enirox001 commented at 12:23 PM on August 6, 2026:

    Thanks, this does address the decodable payload, but looking at it, I wonder whether IsHex() would reject it immediately.

    There is a line in the method

    if (HexDigit(c) < 0) return false
    

    This would imply that the leading z causes IsHex() to return false after inspecting only the first character, regardless of the payload size.

    The existing test most likely would not catch this difference because it only checks the externally visible result, which is still the expected decode error. It does not check how much of the payload was processed.

    I still think it may be better to append the non hex character:

    That would still guarantee that the payload is undecodable, but IsHex() would have to inspect the entire large payload before reaching the invalid character.


    maflcko commented at 12:39 PM on August 6, 2026:

    I think we only care about the server here, not about any RPC-method internal logic.

    So I think it is fine?


    enirox001 commented at 12:42 PM on August 6, 2026:

    Okay, that's fine then. Thanks for clarifying this

  43. enirox001 commented at 10:19 AM on August 6, 2026: contributor

    Concept ACK.

    I think it makes sense to add coverage ensuring RPC requests with large payloads are either handled or rejected without timing out, including when the tests run through bitcoin-cli. I left a few questions and nits, mainly around making the randomized test input deterministic.

  44. test: Map cli CalledProcessError on server error to JSONRPCException
    Like authproxy.py, so that tests can work without having to think whether the cli was used or not.
    fa2bd96cc0
  45. test: Check that RPCs do not time out, even under load
    Also, modify send_cli, so that the test can be run under --usecli
    fa7bc26d12
  46. maflcko force-pushed on Aug 6, 2026
  47. maflcko commented at 11:37 AM on August 6, 2026: member

    Rebased and addressed nits. Shoudl be trivial to re-review with range-diff:

    git range-diff bitcoin-core/master fa04bad48b fa7bc26d12
    
  48. DrahtBot requested review from sedited on Aug 6, 2026
  49. sedited approved
  50. sedited commented at 12:59 PM on August 6, 2026: contributor

    ACK fa7bc26d1276581aac795daf8ceaea903cdcd7b3

  51. rkrux commented at 1:10 PM on August 6, 2026: contributor

    Concept ACK

  52. sedited merged this on Aug 6, 2026
  53. sedited closed this on Aug 6, 2026

  54. maflcko deleted the branch on Aug 6, 2026

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-08-24 07:51 UTC

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