-
Notifications
You must be signed in to change notification settings - Fork 417
Implement method to submit package of transactions (submitpackage call) #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ docs/_build | |
| /dist | ||
| /electrumx.egg-info | ||
| /e_x.egg-info | ||
| /venv | ||
| .vscode/ | ||
| .mypy_cache/ | ||
| .idea/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,10 +15,11 @@ | |||||||||
| import os | ||||||||||
| import ssl | ||||||||||
| import time | ||||||||||
| import traceback | ||||||||||
| from collections import defaultdict | ||||||||||
| from functools import partial | ||||||||||
| from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network | ||||||||||
| from typing import Optional, TYPE_CHECKING | ||||||||||
| from typing import Optional, TYPE_CHECKING, List | ||||||||||
| import asyncio | ||||||||||
|
|
||||||||||
| import attr | ||||||||||
|
|
@@ -32,7 +33,7 @@ | |||||||||
| from electrumx.lib.lrucache import LRUCache | ||||||||||
| from electrumx.lib.util import OldTaskGroup | ||||||||||
| from electrumx.lib.hash import (HASHX_LEN, Base58Error, hash_to_hex_str, | ||||||||||
| hex_str_to_hash, sha256) | ||||||||||
| hex_str_to_hash, sha256, double_sha256) | ||||||||||
| from electrumx.lib.merkle import MerkleCache | ||||||||||
| from electrumx.lib.text import sessions_lines | ||||||||||
| from electrumx.server.daemon import DaemonError | ||||||||||
|
|
@@ -786,6 +787,11 @@ async def broadcast_transaction(self, raw_tx): | |||||||||
| self.txs_sent += 1 | ||||||||||
| return hex_hash | ||||||||||
|
|
||||||||||
| async def broadcast_package(self, tx_package: List[str]) -> dict: | ||||||||||
| result = await self.daemon.broadcast_package(tx_package) | ||||||||||
| self.txs_sent += len(tx_package) | ||||||||||
| return result | ||||||||||
|
|
||||||||||
| async def limited_history(self, hashX): | ||||||||||
| '''Returns a pair (history, cost). | ||||||||||
|
|
||||||||||
|
|
@@ -978,7 +984,7 @@ class ElectrumX(SessionBase): | |||||||||
| '''A TCP server that handles incoming Electrum connections.''' | ||||||||||
|
|
||||||||||
| PROTOCOL_MIN = (1, 4) | ||||||||||
| PROTOCOL_MAX = (1, 4, 3) | ||||||||||
| PROTOCOL_MAX = (1, 4, 4) | ||||||||||
|
|
||||||||||
| def __init__(self, *args, **kwargs): | ||||||||||
| super().__init__(*args, **kwargs) | ||||||||||
|
|
@@ -1468,6 +1474,51 @@ async def transaction_broadcast(self, raw_tx): | |||||||||
| self.logger.info(f'sent tx: {hex_hash}') | ||||||||||
| return hex_hash | ||||||||||
|
|
||||||||||
| async def package_broadcast(self, tx_package: List[str], verbose: bool = False) -> dict: | ||||||||||
| """Broadcast a package of raw transactions to the network (submitpackage). | ||||||||||
| The package must consist of a child with its parents, | ||||||||||
| and none of the parents may depend on one another. | ||||||||||
|
|
||||||||||
| raw_txs: a list of raw transactions as hexadecimal strings""" | ||||||||||
| self.bump_cost(0.25 + sum(len(tx) / 5000 for tx in tx_package)) | ||||||||||
| try: | ||||||||||
| txids = [double_sha256(bytes.fromhex(tx)).hex() for tx in tx_package] | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are these txids for? They are in reversed byte order versus normal RPC interface, so logging them isn't that helpful, because it's not like you can copy-paste them to an explorer to view the txns (unless you manually reverse them first). Also, they are wtxids, and not normal txids, because you are hashing the raw txn which contains witness data, rather than the stripped txn...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, good catch. Yeah this is incorrect i remove it in this followup PR #319 |
||||||||||
| except ValueError: | ||||||||||
| self.logger.info(f"error calculating txids: {traceback.format_exc()}") | ||||||||||
|
Comment on lines
+1486
to
+1487
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to import traceback
Suggested change
here is some boilerplate: import traceback
import logging
import sys
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
console_stderr_handler = logging.StreamHandler(sys.stderr)
console_stderr_handler.setLevel(logging.DEBUG)
logger.addHandler(console_stderr_handler)and now compare (in a fresh local python interpreter): try:
txids = [double_sha256(bytes.fromhex(tx)).hex() for tx in tx_package]
except Exception:
logger.info(f"error calculating txids: {traceback.format_exc()}")and try:
txids = [double_sha256(bytes.fromhex(tx)).hex() for tx in tx_package]
except Exception as e:
logger.info(f"error calculating txids", exc_info=e)and try:
txids = [double_sha256(bytes.fromhex(tx)).hex() for tx in tx_package]
except Exception:
logger.info(f"error calculating txids", exc_info=True)see https://docs.python.org/3/library/logging.html#logging.Logger.debug
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that's useful to know! |
||||||||||
| raise RPCError( | ||||||||||
| BAD_REQUEST, | ||||||||||
| f'not a valid hex encoded transaction package: {tx_package}') | ||||||||||
| try: | ||||||||||
| daemon_result = await self.session_mgr.broadcast_package(tx_package) | ||||||||||
| except DaemonError as e: | ||||||||||
| error, = e.args | ||||||||||
| message = error['message'] | ||||||||||
| self.logger.info(f"error submitting package: {message}") | ||||||||||
| raise RPCError(BAD_REQUEST, 'the tx package was rejected by ' | ||||||||||
| f'network rules.\n\n{message}. Package txids: {txids}') | ||||||||||
| else: | ||||||||||
| self.txs_sent += len(tx_package) | ||||||||||
| self.logger.info(f'broadcasted package: {txids}') | ||||||||||
| if verbose: | ||||||||||
| return daemon_result | ||||||||||
| errors = [] | ||||||||||
| for tx in daemon_result.get('tx-results', {}).values(): | ||||||||||
| if tx.get('error'): | ||||||||||
| error_msg = { | ||||||||||
| 'txid': tx.get('txid'), | ||||||||||
| 'error': tx['error'] | ||||||||||
| } | ||||||||||
| errors.append(error_msg) | ||||||||||
| # check both, package_msg and package-msg due to ongoing discussion to change rpc | ||||||||||
| # https://github.com/bitcoin/bitcoin/pull/31900 | ||||||||||
| package_msg = daemon_result.get('package_msg', daemon_result.get('package-msg')) | ||||||||||
| electrumx_result = { | ||||||||||
| 'success': True if package_msg == 'success' else False | ||||||||||
| } | ||||||||||
| if errors: | ||||||||||
| electrumx_result['errors'] = errors | ||||||||||
| return electrumx_result | ||||||||||
|
|
||||||||||
| async def transaction_get(self, tx_hash, verbose=False): | ||||||||||
| '''Return the serialized raw transaction given its hash | ||||||||||
|
|
||||||||||
|
|
@@ -1555,7 +1606,8 @@ def set_request_handlers(self, ptuple): | |||||||||
|
|
||||||||||
| if ptuple >= (1, 4, 2): | ||||||||||
| handlers['blockchain.scripthash.unsubscribe'] = self.scripthash_unsubscribe | ||||||||||
|
|
||||||||||
| if ptuple >= (1, 4, 4): | ||||||||||
| handlers['blockchain.transaction.broadcast_package'] = self.package_broadcast | ||||||||||
|
Comment on lines
+1609
to
+1610
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's either not expose the new method at all, or maybe expose it unconditionally by moving it ~6 lines higher into the big dict. I don't want to gate it behind an arbitrary temporary version number. Once spesmilo/electrum-protocol#2 is done, we can gate it behind that version number along with the other changes. Until it is documented in the spec, clients should not rely on this method existing at any version. |
||||||||||
| self.request_handlers = handlers | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
||||||||||
Uh oh!
There was an error while loading. Please reload this page.