rpc: clarify sendrawtransaction decode error #36250

pull MrHodlX wants to merge 2 commits into bitcoin:master from MrHodlX:rpc-clarify-sendrawtransaction-decode-error changing 3 files +11 −2
  1. MrHodlX commented at 3:26 PM on September 14, 2026: none

    The existing sendrawtransaction decode error suggests that the transaction may have no inputs. The same error is returned for every DecodeHexTx() failure, including invalid hex and incomplete or malformed serialization.

    Mention encoding and serialization requirements while retaining the existing input hint. Transaction processing and error codes are unchanged.

  2. DrahtBot added the label RPC/REST/ZMQ on Sep 14, 2026
  3. DrahtBot commented at 3:26 PM on September 14, 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/36250.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    ACK l0rinc, Rob1Ham

    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

    No conflicts as of last run.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. in src/rpc/mempool.cpp:131 in e768f6e9a3 outdated
     127 | @@ -128,7 +128,9 @@ static RPCMethod sendrawtransaction()
     128 |  
     129 |              CMutableTransaction mtx;
     130 |              if (!DecodeHexTx(mtx, request.params[0].get_str())) {
     131 | -                throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
     132 | +                throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
    


    Rob1Ham commented at 6:14 PM on September 14, 2026:

    DecodeHexTx() checks whether the value is valid hex and whether it can be fully deserialized as a transaction. It does not explicitly check that vin is nonempty. If an empty-input transaction does deserialize successfully, it is rejected later by CheckTransaction() as bad-txns-vin-empty. Some empty-input serializations can also be ambiguous with the witness marker, but that is only one possible reason for a decode failure.

    Because the same message is returned for all DecodeHexTx() failures, I think the message should describe the general failure instead of keeping the input hint.

    I would change the message to:

    throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
                       "TX decode failed. Make sure the transaction is complete and correctly serialized "
                       "as hexadecimal data.");
    

    MrHodlX commented at 12:51 AM on September 15, 2026:

    Applied in 42675f617, with one adjustment from l0rinc's review: the input hint was added deliberately in #19836 for zero-input decode failures, so the final message retains it: "TX decode failed. Make sure the transaction is complete, correctly serialized, hex-encoded, and has at least one input."

  5. in test/functional/rpc_echo_payload.py:38 in e768f6e9a3
      34 | @@ -35,7 +35,8 @@ def check_results(rpc):
      35 |                  except JSONRPCException as e:
      36 |                      msg = e.error["message"]
      37 |                      if msg not in [
      38 | -                        "TX decode failed. Make sure the tx has at least one input.",
      39 | +                        "TX decode failed. Make sure the transaction is provided as complete, correctly "
    


    Rob1Ham commented at 6:14 PM on September 14, 2026:

    To conform with the mempool.cpp review, I'd update this to:

    "TX decode failed. Make sure the transaction is complete and correctly serialized "
    "as hexadecimal data.",
    

    MrHodlX commented at 12:51 AM on September 15, 2026:

    Updated in 42675f617 to expect the final message ("...complete, correctly serialized, hex-encoded, and has at least one input.").

  6. Rob1Ham commented at 6:26 PM on September 14, 2026: contributor

    Thanks for working on this. The current message still places too much emphasis on the transaction having an input.

    DecodeHexTx() does not explicitly require a nonempty input vector. An empty-input transaction that successfully decodes is rejected later by CheckTransaction() as bad-txns-vin-empty. I think the decode error should describe the general serialization failure without retaining the input hint.

    Could you also add focused coverage to RawTransactionsTest.sendrawtransaction_tests() in test/functional/rpc_rawtransaction.py for representative invalid encodings and incomplete serializations? For example:

    @@
         def sendrawtransaction_tests(self):
    +        self.log.info("Test sendrawtransaction with malformed transaction data")
    +        decode_error = (
    +            "TX decode failed. Make sure the transaction is complete and correctly serialized "
    +            "as hexadecimal data."
    +        )
    +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, "not-hex")
    +
             self.log.info("Test sendrawtransaction with missing input")
             inputs = [{'txid': TXID, 'vout': 1}]  # won't exist
             address = getnewdestination()[2]
             outputs = {address: 4.998}
             rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
    +
    +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, rawtx[:-2])
    +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, rawtx + "00")
             assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx)
    

    This leaves the existing missing-input assertion and log message in place while adding direct coverage for invalid hex, truncation, and trailing bytes.

  7. MrHodlX force-pushed on Sep 14, 2026
  8. MrHodlX force-pushed on Sep 14, 2026
  9. Rob1Ham commented at 6:45 PM on September 14, 2026: contributor

    ACK 663d1fe2

    Built locally and ran build-pr36250/test/functional/test_runner.py rpc_rawtransaction.py rpc_echo_payload.py. Both tests passed.

    Edit: Also ran test_bitcoin, all clear.

  10. MrHodlX force-pushed on Sep 14, 2026
  11. MrHodlX commented at 9:21 PM on September 14, 2026: none

    Commits reworked to satisfy the "test ancestor commits" CI job — no change to the overall diff.

    Previously the first commit changed the error message in src/rpc/mempool.cpp while the matching rpc_echo_payload.py expectation update lived in the second commit, so the job failed when testing the first commit on its own (AssertionError: Unexpected msg: TX decode failed. ...).

    The split is now:

    1. c8dd876ec — message change + the rpc_echo_payload.py expectation update it requires (self-contained, passes on its own)
    2. fec99e442 — the new malformed-payload assertions in rpc_rawtransaction.py only
  12. DrahtBot added the label CI failed on Sep 14, 2026
  13. in test/functional/rpc_rawtransaction.py:369 in fec99e4424 outdated
     364 |          inputs = [{'txid': TXID, 'vout': 1}]  # won't exist
     365 |          address = getnewdestination()[2]
     366 |          outputs = {address: 4.998}
     367 |          rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
     368 | +
     369 | +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, rawtx[:-2])
    


    l0rinc commented at 9:47 PM on September 14, 2026:

    fec99e4 test: cover sendrawtransaction decode failures:

    This is valid hex, but decoding runs out of bytes here (consider adding a same-line comment to clarify why this is needed).

  14. in test/functional/rpc_rawtransaction.py:370 in fec99e4424 outdated
     365 |          address = getnewdestination()[2]
     366 |          outputs = {address: 4.998}
     367 |          rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
     368 | +
     369 | +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, rawtx[:-2])
     370 | +        assert_raises_rpc_error(-22, decode_error, self.nodes[2].sendrawtransaction, rawtx + "00")
    


    l0rinc commented at 9:48 PM on September 14, 2026:

    fec99e4 test: cover sendrawtransaction decode failures:

    nit: this is also valid hex, but has superfluous data; could add a commit on top or on the same line stating that this is testing valid hex but an invalid tx

  15. in test/functional/rpc_rawtransaction.py:360 in fec99e4424 outdated
     352 | @@ -353,11 +353,21 @@ def createrawtransaction_tests(self):
     353 |              assert_equal(tx.version, version)
     354 |  
     355 |      def sendrawtransaction_tests(self):
     356 | +        self.log.info("Test sendrawtransaction with malformed transaction data")
     357 | +        decode_error = (
     358 | +            "TX decode failed. Make sure the transaction is complete and correctly serialized "
     359 | +            "as hexadecimal data."
     360 | +        )
    


    l0rinc commented at 9:49 PM on September 14, 2026:

    fec99e4 test: cover sendrawtransaction decode failures:

    nit: alternatively this can also be a single line if you want in most cases in this PR (to minimize the diffs):

            decode_error = "TX decode failed.[...]"
    
  16. in src/rpc/mempool.cpp:132 in fec99e4424 outdated
     127 | @@ -128,7 +128,9 @@ static RPCMethod sendrawtransaction()
     128 |  
     129 |              CMutableTransaction mtx;
     130 |              if (!DecodeHexTx(mtx, request.params[0].get_str())) {
     131 | -                throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
     132 | +                throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
     133 | +                                   "TX decode failed. Make sure the transaction is complete and correctly serialized "
    


    l0rinc commented at 9:56 PM on September 14, 2026:

    c8dd876 rpc: clarify sendrawtransaction decode error:

    This was added in #19836 and based on the discussions the non-empty case was important to them. Also, serialization is separate from hex conversion, consider:

                                       "TX decode failed. Make sure the transaction is complete, correctly serialized, hex-encoded, and has at least one input."
    
  17. l0rinc changes_requested
  18. l0rinc commented at 10:03 PM on September 14, 2026: contributor

    Concept ACK The same error covers invalid hex, incomplete serialization, and trailing bytes, so broadening the message makes sense. The new tests cover these distinct failures while preserving the existing missing-input validation check.

    The input hint was deliberately added in #19836 (https://github.com/bitcoin/bitcoin/pull/19836#issuecomment-685872256) to explain decoding failures involving zero-input transactions. I’d retain that troubleshooting guidance alongside the general requirements, with serialization and hex encoding described separately.

    The PR description already says the input hint is retained, which would match that suggestion. “Transaction processing and error codes are unchanged” would be more precise than “RPC behavior and error codes are unchanged.” Or just remove it. LLMs are eager to add these but I don't think they're very useful.

  19. in test/functional/rpc_echo_payload.py:38 in 234e4d5e59 outdated
      34 | @@ -35,8 +35,7 @@ def check_results(rpc):
      35 |                  except JSONRPCException as e:
      36 |                      msg = e.error["message"]
      37 |                      if msg not in [
      38 | -                        "TX decode failed. Make sure the transaction is complete and correctly serialized "
      39 | -                        "as hexadecimal data.",
      40 | +                        "TX decode failed. Make sure the transaction is complete, correctly serialized, hex-encoded, and has at least one input.",
    


    l0rinc commented at 10:20 PM on September 14, 2026:

    234e4d5 rpc: restore input hint in sendrawtransaction decode error:

    Please rebase the changes so that the PR looks like this was the plan all along; no need to keep the diverging versions. You can expect the reviewers to review commit by commit, so they won't like if it changes back and forth.

  20. rpc: clarify sendrawtransaction decode error
    The same error is returned for every DecodeHexTx() failure, including
    invalid hex and incomplete or malformed serialization, so mention the
    serialization and hex encoding requirements alongside the existing
    input hint (added deliberately in #19836 for zero-input transaction
    decode failures).
    
    Update the rpc_echo_payload expectation, which asserts the exact
    message.
    42675f617b
  21. test: cover sendrawtransaction decode failures
    Add assertions for invalid hex, truncated, and trailing-byte payloads
    in sendrawtransaction_tests.
    94a616d700
  22. in test/functional/rpc_rawtransaction.py:1 in aba1017ea0
       0 | @@ -1 +1,13 @@
       1 | -PLACEHOLDER
    


    l0rinc commented at 10:21 PM on September 14, 2026:

    We're getting notifications about every push, please only do so when the PR is ready to review again :)

  23. MrHodlX force-pushed on Sep 14, 2026
  24. MrHodlX referenced this in commit 9d2ca4e66b on Sep 14, 2026
  25. l0rinc commented at 12:45 AM on September 15, 2026: contributor

    ACK 94a616d7000371a147112b4364db49372c74bad4

    To simplify it for the other reviewers, please go through the review comments that are already addressed and resolved them manually.

  26. DrahtBot requested review from Rob1Ham on Sep 15, 2026
  27. Rob1Ham commented at 1:33 AM on September 15, 2026: contributor

    re-ACK 94a616d

    Built and tested each commit locally. All unit tests passed, and applicable functional tests passed.

  28. DrahtBot removed the label CI failed on Sep 15, 2026
  29. l0rinc approved
  30. maflcko commented at 5:19 AM on September 16, 2026: member

    The input hint was deliberately added in #19836 (#19836 (comment)) to explain decoding failures involving zero-input transactions.

    Yeah, this was done intentionally, when the heuristic fails. So there are two error cases handled in the same error message:

    • TX decode failed.
    • Someone provided a "valid" zero-input tx, which also failed witness deser.

    I understand the error message can be a bit confusing, but I don't understand this pull request. What is so special about sendrawtransaction?

  31. MrHodlX commented at 10:08 AM on September 16, 2026: none

    Nothing special about sendrawtransaction itself. The problem is the message is attached to every DecodeHexTx() failure, not only the zero-input / witness-heuristic case from #19836.

    DecodeHexTx() returns false for invalid hex, truncated hex, extra trailing bytes, and the ambiguous empty-vin / witness-marker case. All of those currently say “Make sure the tx has at least one input.” A truncated or non-hex payload does not have an input problem; bad-txns-vin-empty is the later CheckTransaction() path when an empty-vin tx actually deserializes.

    The #19836 hint is still there. This only names the other DecodeHexTx() failure modes so the same string is not a false lead for the common cases.

    Happy to drop the input clause again if reviewers prefer a general decode message and leave the empty-vin case to CheckTransaction().

  32. maflcko commented at 11:54 AM on September 16, 2026: member

    Well, invalid hex or truncated hex is not a valid tx, so the tx doesn't have at least one input, so the current error msg is perfectly fine :trollface:

    In any case, either this doesn't matter, in which case this special-casing here should be closed, or it does matter, in which case it should be fixed in all places.

  33. MrHodlX commented at 12:10 PM on September 16, 2026: none

    That's the plan — #36263 applies the same message to the remaining DecodeHexTx() error paths (testmempoolaccept, submitpackage, generateblock, combinerawtransaction, signrawtransactionwithkey, signrawtransactionwithwallet, importprunedfunds). It's stacked on this PR and marked draft until this one merges, per l0rinc's request. This PR is the first, smallest step so the wording gets agreed once before it's replicated.

  34. maflcko commented at 1:48 PM on September 16, 2026: member

    Please do not use LLM generated comments, per https://github.com/bitcoin/bitcoin/blob/master/doc/AI_POLICY.md

    In any case, I'll close this for now. A single pull request for this should be enough.

    If you are worried about duplication, the string can be extracted into a single place (e.g. inline constexpr string_view in a header).

  35. maflcko closed this on Sep 16, 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-09-21 02:52 UTC

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