network: move broadcast_transaction move network to interface.py
What changed, and why it matters
This commit is a straightforward code reorganization: it moves the transaction-broadcasting logic and its error-handling helpers from one internal file (network.py) to another (interface.py). No security behavior appears to change; the same checks, error messages, and server-response sanitization are preserved.
No security action required; treat as routine refactoring. Reviewers may verify that all imports and exception references were updated correctly.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch relocates broadcast_transaction(), sanitize_tx_broadcast_response(), and the TxBroadcastError exception hierarchy from electrum/network.py to electrum/interface.py. It updates imports accordingly and changes Network.broadcast_transaction() into a thin wrapper that delegates to self.interface.broadcast_transaction(). The logic, including DummyAddress checks, timeout handling, aiorpcx error mapping, hash-mismatch detection, and substring-based untrusted-server-message sanitization, is copied verbatim with no functional modifications.
Changed components
electrum/interface.pyelectrum/network.pyInspect captured patch +241 / −236
diff --git a/electrum/interface.py b/electrum/interface.py
index e47edd0..c9f438e 100644
--- a/electrum/interface.py
+++ b/electrum/interface.py
@@ -49,7 +49,8 @@ import certifi
from .util import (ignore_exceptions, log_exceptions, bfh, ESocksProxy,
is_integer, is_non_negative_integer, is_hash256_str, is_hex_str,
- is_int_or_float, is_non_negative_int_or_float, OldTaskGroup)
+ is_int_or_float, is_non_negative_int_or_float, OldTaskGroup,
+ send_exception_to_crash_reporter, error_text_str_to_safe_str)
from . import util
from . import x509
from . import pem
@@ -57,6 +58,7 @@ from . import version
from . import blockchain
from .blockchain import Blockchain, HEADER_SIZE, CHUNK_SIZE
from . import bitcoin
+from .bitcoin import DummyAddress, DummyAddressUsedInTxException
from . import constants
from .i18n import _
from .logging import Logger
@@ -279,6 +281,34 @@ class InvalidOptionCombination(Exception): pass
class ConnectError(NetworkException): pass
+class TxBroadcastError(NetworkException):
+ def get_message_for_gui(self):
+ raise NotImplementedError()
+
+
+class TxBroadcastHashMismatch(TxBroadcastError):
+ def get_message_for_gui(self):
+ return "{}\n{}\n\n{}" \
+ .format(_("The server returned an unexpected transaction ID when broadcasting the transaction."),
+ _("Consider trying to connect to a different server, or updating Electrum."),
+ str(self))
+
+
+class TxBroadcastServerReturnedError(TxBroadcastError):
+ def get_message_for_gui(self):
+ return "{}\n{}\n\n{}" \
+ .format(_("The server returned an error when broadcasting the transaction."),
+ _("Consider trying to connect to a different server, or updating Electrum."),
+ str(self))
+
+
+class TxBroadcastUnknownError(TxBroadcastError):
+ def get_message_for_gui(self):
+ return "{}\n{}" \
+ .format(_("Unknown error when broadcasting the transaction."),
+ _("Consider trying to connect to a different server, or updating Electrum."))
+
+
class _RSClient(RSClient):
async def create_connection(self):
try:
@@ -1296,6 +1326,29 @@ class Interface(Logger):
raise RequestCorrupted(f"received tx does not match expected txid {tx_hash} (got {tx.txid()})")
return raw
+ async def broadcast_transaction(self, tx: 'Transaction', *, timeout=None) -> None:
+ """caller should handle TxBroadcastError and RequestTimedOut"""
+ if timeout is None:
+ timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Urgent)
+ if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()):
+ raise DummyAddressUsedInTxException("tried to broadcast tx with dummy address!")
+ try:
+ out = await self.session.send_request('blockchain.transaction.broadcast', [tx.serialize()], timeout=timeout)
+ # note: both 'out' and exception messages are untrusted input from the server
+ except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError):
+ raise # pass-through
+ except aiorpcx.jsonrpc.CodeMessageError as e:
+ self.logger.info(f"broadcast_transaction error [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(e))}. tx={str(tx)}")
+ raise TxBroadcastServerReturnedError(sanitize_tx_broadcast_response(e.message)) from e
+ except BaseException as e: # intentional BaseException for sanity!
+ self.logger.info(f"broadcast_transaction error2 [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(e))}. tx={str(tx)}")
+ send_exception_to_crash_reporter(e)
+ raise TxBroadcastUnknownError() from e
+ if out != tx.txid():
+ self.logger.info(f"unexpected txid for broadcast_transaction [DO NOT TRUST THIS MESSAGE]: "
+ f"{error_text_str_to_safe_str(out)} != {tx.txid()}. tx={str(tx)}")
+ raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID."))
+
async def get_history_for_scripthash(self, sh: str) -> List[dict]:
if not is_hash256_str(sh):
raise Exception(f"{repr(sh)} is not a scripthash")
@@ -1462,6 +1515,190 @@ def _assert_header_does_not_check_against_any_chain(header: dict) -> None:
raise Exception('bad_header must not check!')
+def sanitize_tx_broadcast_response(server_msg) -> str:
+ # Unfortunately, bitcoind and hence the Electrum protocol doesn't return a useful error code.
+ # So, we use substring matching to grok the error message.
+ # server_msg is untrusted input so it should not be shown to the user. see #4968
+ server_msg = str(server_msg)
+ server_msg = server_msg.replace("\n", r"\n")
+
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/script/script_error.cpp
+ script_error_messages = {
+ r"Script evaluated without error but finished with a false/empty top stack element",
+ r"Script failed an OP_VERIFY operation",
+ r"Script failed an OP_EQUALVERIFY operation",
+ r"Script failed an OP_CHECKMULTISIGVERIFY operation",
+ r"Script failed an OP_CHECKSIGVERIFY operation",
+ r"Script failed an OP_NUMEQUALVERIFY operation",
+ r"Script is too big",
+ r"Push value size limit exceeded",
+ r"Operation limit exceeded",
+ r"Stack size limit exceeded",
+ r"Signature count negative or greater than pubkey count",
+ r"Pubkey count negative or limit exceeded",
+ r"Opcode missing or not understood",
+ r"Attempted to use a disabled opcode",
+ r"Operation not valid with the current stack size",
+ r"Operation not valid with the current altstack size",
+ r"OP_RETURN was encountered",
+ r"Invalid OP_IF construction",
+ r"Negative locktime",
+ r"Locktime requirement not satisfied",
+ r"Signature hash type missing or not understood",
+ r"Non-canonical DER signature",
+ r"Data push larger than necessary",
+ r"Only push operators allowed in signatures",
+ r"Non-canonical signature: S value is unnecessarily high",
+ r"Dummy CHECKMULTISIG argument must be zero",
+ r"OP_IF/NOTIF argument must be minimal",
+ r"Signature must be zero for failed CHECK(MULTI)SIG operation",
+ r"NOPx reserved for soft-fork upgrades",
+ r"Witness version reserved for soft-fork upgrades",
+ r"Taproot version reserved for soft-fork upgrades",
+ r"OP_SUCCESSx reserved for soft-fork upgrades",
+ r"Public key version reserved for soft-fork upgrades",
+ r"Public key is neither compressed or uncompressed",
+ r"Stack size must be exactly one after execution",
+ r"Extra items left on stack after execution",
+ r"Witness program has incorrect length",
+ r"Witness program was passed an empty witness",
+ r"Witness program hash mismatch",
+ r"Witness requires empty scriptSig",
+ r"Witness requires only-redeemscript scriptSig",
+ r"Witness provided for non-witness script",
+ r"Using non-compressed keys in segwit",
+ r"Invalid Schnorr signature size",
+ r"Invalid Schnorr signature hash type",
+ r"Invalid Schnorr signature",
+ r"Invalid Taproot control block size",
+ r"Too much signature validation relative to witness weight",
+ r"OP_CHECKMULTISIG(VERIFY) is not available in tapscript",
+ r"OP_IF/NOTIF argument must be minimal in tapscript",
+ r"Using OP_CODESEPARATOR in non-witness script",
+ r"Signature is found in scriptCode",
+ }
+ for substring in script_error_messages:
+ if substring in server_msg:
+ return substring
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/validation.cpp
+ # grep "REJECT_"
+ # grep "TxValidationResult"
+ # should come after script_error.cpp (due to e.g. "non-mandatory-script-verify-flag")
+ validation_error_messages = {
+ r"coinbase": None,
+ r"tx-size-small": None,
+ r"non-final": None,
+ r"txn-already-in-mempool": None,
+ r"txn-mempool-conflict": None,
+ r"txn-already-known": None,
+ r"non-BIP68-final": None,
+ r"bad-txns-nonstandard-inputs": None,
+ r"bad-witness-nonstandard": None,
+ r"bad-txns-too-many-sigops": None,
+ r"mempool min fee not met":
+ ("mempool min fee not met\n" +
+ _("Your transaction is paying a fee that is so low that the bitcoin node cannot "
+ "fit it into its mempool. The mempool is already full of hundreds of megabytes "
+ "of transactions that all pay higher fees. Try to increase the fee.")),
+ r"min relay fee not met": None,
+ r"absurdly-high-fee": None,
+ r"max-fee-exceeded": None,
+ r"too-long-mempool-chain": None,
+ r"bad-txns-spends-conflicting-tx": None,
+ r"insufficient fee": ("insufficient fee\n" +
+ _("Your transaction is trying to replace another one in the mempool but it "
+ "does not meet the rules to do so. Try to increase the fee.")),
+ r"too many potential replacements": None,
+ r"replacement-adds-unconfirmed": None,
+ r"mempool full": None,
+ r"non-mandatory-script-verify-flag": None,
+ r"mandatory-script-verify-flag-failed": None,
+ r"Transaction check failed": None,
+ }
+ for substring in validation_error_messages:
+ if substring in server_msg:
+ msg = validation_error_messages[substring]
+ return msg if msg else substring
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/rpc/rawtransaction.cpp
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/util/error.cpp
+ # https://github.com/bitcoin/bitcoin/blob/3f83c744ac28b700090e15b5dda2260724a56f49/src/common/messages.cpp#L126
+ # grep "RPC_TRANSACTION"
+ # grep "RPC_DESERIALIZATION_ERROR"
+ # grep "TransactionError"
+ rawtransaction_error_messages = {
+ r"Missing inputs": None,
+ r"Inputs missing or spent": None,
+ r"transaction already in block chain": None,
+ r"Transaction already in block chain": None,
+ r"Transaction outputs already in utxo set": None,
+ r"TX decode failed": None,
+ r"Peer-to-peer functionality missing or disabled": None,
+ r"Transaction rejected by AcceptToMemoryPool": None,
+ r"AcceptToMemoryPool failed": None,
+ r"Transaction rejected by mempool": None,
+ r"Mempool internal error": None,
+ r"Fee exceeds maximum configured by user": None,
+ r"Unspendable output exceeds maximum configured by user": None,
+ r"Transaction rejected due to invalid package": None,
+ }
+ for substring in rawtransaction_error_messages:
+ if substring in server_msg:
+ msg = rawtransaction_error_messages[substring]
+ return msg if msg else substring
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/consensus/tx_verify.cpp
+ # https://github.com/bitcoin/bitcoin/blob/c7ad94428ab6f54661d7a5441e1fdd0ebf034903/src/consensus/tx_check.cpp
+ # grep "REJECT_"
+ # grep "TxValidationResult"
+ tx_verify_error_messages = {
+ r"bad-txns-vin-empty": None,
+ r"bad-txns-vout-empty": None,
+ r"bad-txns-oversize": None,
+ r"bad-txns-vout-negative": None,
+ r"bad-txns-vout-toolarge": None,
+ r"bad-txns-txouttotal-toolarge": None,
+ r"bad-txns-inputs-duplicate": None,
+ r"bad-cb-length": None,
+ r"bad-txns-prevout-null": None,
+ r"bad-txns-inputs-missingorspent":
+ ("bad-txns-inputs-missingorspent\n" +
+ _("You might have a local transaction in your wallet that this transaction "
+ "builds on top. You need to either broadcast or remove the local tx.")),
+ r"bad-txns-premature-spend-of-coinbase": None,
+ r"bad-txns-inputvalues-outofrange": None,
+ r"bad-txns-in-belowout": None,
+ r"bad-txns-fee-outofrange": None,
+ }
+ for substring in tx_verify_error_messages:
+ if substring in server_msg:
+ msg = tx_verify_error_messages[substring]
+ return msg if msg else substring
+ # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/policy/policy.cpp
+ # grep "reason ="
+ # should come after validation.cpp (due to "tx-size" vs "tx-size-small")
+ # should come after script_error.cpp (due to e.g. "version")
+ policy_error_messages = {
+ r"version": _("Transaction uses non-standard version."),
+ r"tx-size": _("The transaction was rejected because it is too large (in bytes)."),
+ r"scriptsig-size": None,
+ r"scriptsig-not-pushonly": None,
+ r"scriptpubkey":
+ ("scriptpubkey\n" +
+ _("Some of the outputs pay to a non-standard script.")),
+ r"bare-multisig": None,
+ r"dust":
+ (_("Transaction could not be broadcast due to dust outputs.\n"
+ "Some of the outputs are too small in value, probably lower than 1000 satoshis.\n"
+ "Check the units, make sure you haven't confused e.g. mBTC and BTC.")),
+ r"multi-op-return": _("The transaction was rejected because it contains multiple OP_RETURN outputs."),
+ }
+ for substring in policy_error_messages:
+ if substring in server_msg:
+ msg = policy_error_messages[substring]
+ return msg if msg else substring
+ # otherwise:
+ return _("Unknown error")
+
+
def check_cert(host, cert):
try:
b = pem.dePem(cert, 'CERTIFICATE')
diff --git a/electrum/network.py b/electrum/network.py
index 462cdaa..b9a9bb7 100644
--- a/electrum/network.py
+++ b/electrum/network.py
@@ -43,10 +43,9 @@ from aiohttp import ClientResponse
from . import util
from .util import (
- log_exceptions, ignore_exceptions, OldTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter, MyEncoder,
+ log_exceptions, ignore_exceptions, OldTaskGroup, make_aiohttp_session, MyEncoder,
NetworkRetryManager, error_text_str_to_safe_str, detect_tor_socks_proxy
)
-from .bitcoin import DummyAddress, DummyAddressUsedInTxException
from . import constants
from . import blockchain
from . import dns_hacks
@@ -54,7 +53,7 @@ from .transaction import Transaction
from .blockchain import Blockchain
from .interface import (
Interface, PREFERRED_NETWORK_PROTOCOL, RequestTimedOut, NetworkTimeout, BUCKET_NAME_OF_ONION_SERVERS,
- NetworkException, RequestCorrupted, ServerAddr
+ NetworkException, RequestCorrupted, ServerAddr, TxBroadcastError,
)
from .version import PROTOCOL_VERSION
from .i18n import _
@@ -287,34 +286,6 @@ class NetworkParameters(NamedTuple):
class BestEffortRequestFailed(NetworkException): pass
-class TxBroadcastError(NetworkException):
- def get_message_for_gui(self):
- raise NotImplementedError()
-
-
-class TxBroadcastHashMismatch(TxBroadcastError):
- def get_message_for_gui(self):
- return "{}\n{}\n\n{}" \
- .format(_("The server returned an unexpected transaction ID when broadcasting the transaction."),
- _("Consider trying to connect to a different server, or updating Electrum."),
- str(self))
-
-
-class TxBroadcastServerReturnedError(TxBroadcastError):
- def get_message_for_gui(self):
- return "{}\n{}\n\n{}" \
- .format(_("The server returned an error when broadcasting the transaction."),
- _("Consider trying to connect to a different server, or updating Electrum."),
- str(self))
-
-
-class TxBroadcastUnknownError(TxBroadcastError):
- def get_message_for_gui(self):
- return "{}\n{}" \
- .format(_("Unknown error when broadcasting the transaction."),
- _("Consider trying to connect to a different server, or updating Electrum."))
-
-
class UntrustedServerReturnedError(NetworkException):
def __init__(self, *, original_exception):
self.original_exception = original_exception
@@ -1096,26 +1067,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
"""caller should handle TxBroadcastError"""
if self.interface is None: # handled by best_effort_reliable
raise RequestTimedOut()
- if timeout is None:
- timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent)
- if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()):
- raise DummyAddressUsedInTxException("tried to broadcast tx with dummy address!")
- try:
- out = await self.interface.session.send_request('blockchain.transaction.broadcast', [tx.serialize()], timeout=timeout)
- # note: both 'out' and exception messages are untrusted input from the server
- except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError):
- raise # pass-through
- except aiorpcx.jsonrpc.CodeMessageError as e:
- self.logger.info(f"broadcast_transaction error [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(e))}. tx={str(tx)}")
- raise TxBroadcastServerReturnedError(self.sanitize_tx_broadcast_response(e.message)) from e
- except BaseException as e: # intentional BaseException for sanity!
- self.logger.info(f"broadcast_transaction error2 [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(e))}. tx={str(tx)}")
- send_exception_to_crash_reporter(e)
- raise TxBroadcastUnknownError() from e
- if out != tx.txid():
- self.logger.info(f"unexpected txid for broadcast_transaction [DO NOT TRUST THIS MESSAGE]: "
- f"{error_text_str_to_safe_str(out)} != {tx.txid()}. tx={str(tx)}")
- raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID."))
+ await self.interface.broadcast_transaction(tx, timeout=timeout)
async def try_broadcasting(self, tx, name) -> bool:
try:
@@ -1127,190 +1079,6 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
self.logger.info(f'success: broadcasting {name} {tx.txid()}')
return True
- @staticmethod
- def sanitize_tx_broadcast_response(server_msg) -> str:
- # Unfortunately, bitcoind and hence the Electrum protocol doesn't return a useful error code.
- # So, we use substring matching to grok the error message.
- # server_msg is untrusted input so it should not be shown to the user. see #4968
- server_msg = str(server_msg)
- server_msg = server_msg.replace("\n", r"\n")
-
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/script/script_error.cpp
- script_error_messages = {
- r"Script evaluated without error but finished with a false/empty top stack element",
- r"Script failed an OP_VERIFY operation",
- r"Script failed an OP_EQUALVERIFY operation",
- r"Script failed an OP_CHECKMULTISIGVERIFY operation",
- r"Script failed an OP_CHECKSIGVERIFY operation",
- r"Script failed an OP_NUMEQUALVERIFY operation",
- r"Script is too big",
- r"Push value size limit exceeded",
- r"Operation limit exceeded",
- r"Stack size limit exceeded",
- r"Signature count negative or greater than pubkey count",
- r"Pubkey count negative or limit exceeded",
- r"Opcode missing or not understood",
- r"Attempted to use a disabled opcode",
- r"Operation not valid with the current stack size",
- r"Operation not valid with the current altstack size",
- r"OP_RETURN was encountered",
- r"Invalid OP_IF construction",
- r"Negative locktime",
- r"Locktime requirement not satisfied",
- r"Signature hash type missing or not understood",
- r"Non-canonical DER signature",
- r"Data push larger than necessary",
- r"Only push operators allowed in signatures",
- r"Non-canonical signature: S value is unnecessarily high",
- r"Dummy CHECKMULTISIG argument must be zero",
- r"OP_IF/NOTIF argument must be minimal",
- r"Signature must be zero for failed CHECK(MULTI)SIG operation",
- r"NOPx reserved for soft-fork upgrades",
- r"Witness version reserved for soft-fork upgrades",
- r"Taproot version reserved for soft-fork upgrades",
- r"OP_SUCCESSx reserved for soft-fork upgrades",
- r"Public key version reserved for soft-fork upgrades",
- r"Public key is neither compressed or uncompressed",
- r"Stack size must be exactly one after execution",
- r"Extra items left on stack after execution",
- r"Witness program has incorrect length",
- r"Witness program was passed an empty witness",
- r"Witness program hash mismatch",
- r"Witness requires empty scriptSig",
- r"Witness requires only-redeemscript scriptSig",
- r"Witness provided for non-witness script",
- r"Using non-compressed keys in segwit",
- r"Invalid Schnorr signature size",
- r"Invalid Schnorr signature hash type",
- r"Invalid Schnorr signature",
- r"Invalid Taproot control block size",
- r"Too much signature validation relative to witness weight",
- r"OP_CHECKMULTISIG(VERIFY) is not available in tapscript",
- r"OP_IF/NOTIF argument must be minimal in tapscript",
- r"Using OP_CODESEPARATOR in non-witness script",
- r"Signature is found in scriptCode",
- }
- for substring in script_error_messages:
- if substring in server_msg:
- return substring
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/validation.cpp
- # grep "REJECT_"
- # grep "TxValidationResult"
- # should come after script_error.cpp (due to e.g. "non-mandatory-script-verify-flag")
- validation_error_messages = {
- r"coinbase": None,
- r"tx-size-small": None,
- r"non-final": None,
- r"txn-already-in-mempool": None,
- r"txn-mempool-conflict": None,
- r"txn-already-known": None,
- r"non-BIP68-final": None,
- r"bad-txns-nonstandard-inputs": None,
- r"bad-witness-nonstandard": None,
- r"bad-txns-too-many-sigops": None,
- r"mempool min fee not met":
- ("mempool min fee not met\n" +
- _("Your transaction is paying a fee that is so low that the bitcoin node cannot "
- "fit it into its mempool. The mempool is already full of hundreds of megabytes "
- "of transactions that all pay higher fees. Try to increase the fee.")),
- r"min relay fee not met": None,
- r"absurdly-high-fee": None,
- r"max-fee-exceeded": None,
- r"too-long-mempool-chain": None,
- r"bad-txns-spends-conflicting-tx": None,
- r"insufficient fee": ("insufficient fee\n" +
- _("Your transaction is trying to replace another one in the mempool but it "
- "does not meet the rules to do so. Try to increase the fee.")),
- r"too many potential replacements": None,
- r"replacement-adds-unconfirmed": None,
- r"mempool full": None,
- r"non-mandatory-script-verify-flag": None,
- r"mandatory-script-verify-flag-failed": None,
- r"Transaction check failed": None,
- }
- for substring in validation_error_messages:
- if substring in server_msg:
- msg = validation_error_messages[substring]
- return msg if msg else substring
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/rpc/rawtransaction.cpp
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/util/error.cpp
- # https://github.com/bitcoin/bitcoin/blob/3f83c744ac28b700090e15b5dda2260724a56f49/src/common/messages.cpp#L126
- # grep "RPC_TRANSACTION"
- # grep "RPC_DESERIALIZATION_ERROR"
- # grep "TransactionError"
- rawtransaction_error_messages = {
- r"Missing inputs": None,
- r"Inputs missing or spent": None,
- r"transaction already in block chain": None,
- r"Transaction already in block chain": None,
- r"Transaction outputs already in utxo set": None,
- r"TX decode failed": None,
- r"Peer-to-peer functionality missing or disabled": None,
- r"Transaction rejected by AcceptToMemoryPool": None,
- r"AcceptToMemoryPool failed": None,
- r"Transaction rejected by mempool": None,
- r"Mempool internal error": None,
- r"Fee exceeds maximum configured by user": None,
- r"Unspendable output exceeds maximum configured by user": None,
- r"Transaction rejected due to invalid package": None,
- }
- for substring in rawtransaction_error_messages:
- if substring in server_msg:
- msg = rawtransaction_error_messages[substring]
- return msg if msg else substring
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/consensus/tx_verify.cpp
- # https://github.com/bitcoin/bitcoin/blob/c7ad94428ab6f54661d7a5441e1fdd0ebf034903/src/consensus/tx_check.cpp
- # grep "REJECT_"
- # grep "TxValidationResult"
- tx_verify_error_messages = {
- r"bad-txns-vin-empty": None,
- r"bad-txns-vout-empty": None,
- r"bad-txns-oversize": None,
- r"bad-txns-vout-negative": None,
- r"bad-txns-vout-toolarge": None,
- r"bad-txns-txouttotal-toolarge": None,
- r"bad-txns-inputs-duplicate": None,
- r"bad-cb-length": None,
- r"bad-txns-prevout-null": None,
- r"bad-txns-inputs-missingorspent":
- ("bad-txns-inputs-missingorspent\n" +
- _("You might have a local transaction in your wallet that this transaction "
- "builds on top. You need to either broadcast or remove the local tx.")),
- r"bad-txns-premature-spend-of-coinbase": None,
- r"bad-txns-inputvalues-outofrange": None,
- r"bad-txns-in-belowout": None,
- r"bad-txns-fee-outofrange": None,
- }
- for substring in tx_verify_error_messages:
- if substring in server_msg:
- msg = tx_verify_error_messages[substring]
- return msg if msg else substring
- # https://github.com/bitcoin/bitcoin/blob/5bb64acd9d3ced6e6f95df282a1a0f8b98522cb0/src/policy/policy.cpp
- # grep "reason ="
- # should come after validation.cpp (due to "tx-size" vs "tx-size-small")
- # should come after script_error.cpp (due to e.g. "version")
- policy_error_messages = {
- r"version": _("Transaction uses non-standard version."),
- r"tx-size": _("The transaction was rejected because it is too large (in bytes)."),
- r"scriptsig-size": None,
- r"scriptsig-not-pushonly": None,
- r"scriptpubkey":
- ("scriptpubkey\n" +
- _("Some of the outputs pay to a non-standard script.")),
- r"bare-multisig": None,
- r"dust":
- (_("Transaction could not be broadcast due to dust outputs.\n"
- "Some of the outputs are too small in value, probably lower than 1000 satoshis.\n"
- "Check the units, make sure you haven't confused e.g. mBTC and BTC.")),
- r"multi-op-return": _("The transaction was rejected because it contains multiple OP_RETURN outputs."),
- }
- for substring in policy_error_messages:
- if substring in server_msg:
- msg = policy_error_messages[substring]
- return msg if msg else substring
- # otherwise:
- return _("Unknown error")
-
@best_effort_reliable
@catch_server_exceptions
async def get_transaction(self, tx_hash: str, *, timeout=None) -> str:
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.