|
15 | 15 | import os |
16 | 16 | import ssl |
17 | 17 | import time |
| 18 | +import traceback |
18 | 19 | from collections import defaultdict |
19 | 20 | from functools import partial |
20 | 21 | from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network |
21 | | -from typing import Optional, TYPE_CHECKING |
| 22 | +from typing import Optional, TYPE_CHECKING, List |
22 | 23 | import asyncio |
23 | 24 |
|
24 | 25 | import attr |
@@ -786,6 +787,11 @@ async def broadcast_transaction(self, raw_tx): |
786 | 787 | self.txs_sent += 1 |
787 | 788 | return hex_hash |
788 | 789 |
|
| 790 | + async def broadcast_package(self, tx_package: List[str]) -> dict: |
| 791 | + result = await self.daemon.broadcast_package(tx_package) |
| 792 | + self.txs_sent += len(tx_package) |
| 793 | + return result |
| 794 | + |
789 | 795 | async def limited_history(self, hashX): |
790 | 796 | '''Returns a pair (history, cost). |
791 | 797 |
|
@@ -978,7 +984,7 @@ class ElectrumX(SessionBase): |
978 | 984 | '''A TCP server that handles incoming Electrum connections.''' |
979 | 985 |
|
980 | 986 | PROTOCOL_MIN = (1, 4) |
981 | | - PROTOCOL_MAX = (1, 4, 3) |
| 987 | + PROTOCOL_MAX = (1, 4, 4) |
982 | 988 |
|
983 | 989 | def __init__(self, *args, **kwargs): |
984 | 990 | super().__init__(*args, **kwargs) |
@@ -1468,6 +1474,36 @@ async def transaction_broadcast(self, raw_tx): |
1468 | 1474 | self.logger.info(f'sent tx: {hex_hash}') |
1469 | 1475 | return hex_hash |
1470 | 1476 |
|
| 1477 | + async def package_broadcast(self, tx_package: List[str], verbose: bool = False) -> dict: |
| 1478 | + """Broadcast a package of raw transactions to the network (submitpackage). |
| 1479 | + The package must consist of a child with its parents, |
| 1480 | + and none of the parents may depend on one another. |
| 1481 | +
|
| 1482 | + raw_txs: a list of raw transactions as hexadecimal strings""" |
| 1483 | + self.bump_cost(0.25 + sum(len(tx) / 5000 for tx in tx_package)) |
| 1484 | + try: |
| 1485 | + txids = [sha256(bytes.fromhex(tx)).hex() for tx in tx_package] |
| 1486 | + except ValueError: |
| 1487 | + self.logger.info(f"error calculating txids: {traceback.format_exc()}") |
| 1488 | + raise RPCError( |
| 1489 | + BAD_REQUEST, |
| 1490 | + f'not a valid hex encoded transaction package: {tx_package}') |
| 1491 | + try: |
| 1492 | + result = await self.session_mgr.broadcast_package(tx_package) |
| 1493 | + except DaemonError as e: |
| 1494 | + error, = e.args |
| 1495 | + message = error['message'] |
| 1496 | + self.logger.info(f"error submitting package: {message}") |
| 1497 | + raise RPCError(BAD_REQUEST, 'the tx package was rejected by ' |
| 1498 | + f'network rules.\n\n{message}. Package txids: {txids}') |
| 1499 | + else: |
| 1500 | + self.txs_sent += len(tx_package) |
| 1501 | + self.logger.info(f'broadcasted package: {txids}') |
| 1502 | + if verbose: |
| 1503 | + return result |
| 1504 | + return {'package_msg': result.get('package_msg', ''), |
| 1505 | + 'replaced-transactions': result.get('replaced_txs', [])} |
| 1506 | + |
1471 | 1507 | async def transaction_get(self, tx_hash, verbose=False): |
1472 | 1508 | '''Return the serialized raw transaction given its hash |
1473 | 1509 |
|
@@ -1555,7 +1591,8 @@ def set_request_handlers(self, ptuple): |
1555 | 1591 |
|
1556 | 1592 | if ptuple >= (1, 4, 2): |
1557 | 1593 | handlers['blockchain.scripthash.unsubscribe'] = self.scripthash_unsubscribe |
1558 | | - |
| 1594 | + if ptuple >= (1, 4, 4): |
| 1595 | + handlers['blockchain.transaction.broadcast_package'] = self.package_broadcast |
1559 | 1596 | self.request_handlers = handlers |
1560 | 1597 |
|
1561 | 1598 |
|
|
0 commit comments