diff --git a/electrum/interface.py b/electrum/interface.py index e47edd024..c9f438e47 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 462cdaa74..b9a9bb7a7 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: