Skip to content

Commit d39138b

Browse files
f321xf321x
authored andcommitted
implement submitpackage call
rebase on master make try except more concise, add verbose param to package_broadcast make try except more concise, add verbose param to package_broadcast fix replaced-transactions key in return value implement submitpackage call
1 parent b293369 commit d39138b

File tree

4 files changed

+52
-4
lines changed

4 files changed

+52
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ docs/_build
1010
/dist
1111
/electrumx.egg-info
1212
/e_x.egg-info
13+
/venv
1314
.vscode/
1415
.mypy_cache/
1516
.idea/

electrumx/server/daemon.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import time
1414
from calendar import timegm
1515
from struct import pack
16-
from typing import TYPE_CHECKING, Type
16+
from typing import TYPE_CHECKING, Type, List
1717

1818
import aiohttp
1919
from aiorpcx import JSONRPC
@@ -310,6 +310,10 @@ async def broadcast_transaction(self, raw_tx):
310310
'''Broadcast a transaction to the network.'''
311311
return await self._send_single('sendrawtransaction', (raw_tx, ))
312312

313+
async def broadcast_package(self, raw_txs: List[str]):
314+
"""Broadcast a package of transactions to the network using 'submitpackage'."""
315+
return await self._send_single('submitpackage', (raw_txs, ))
316+
313317
async def height(self):
314318
'''Query the daemon for its current height.'''
315319
self._height = await self._send_single('getblockcount')

electrumx/server/session.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
import os
1616
import ssl
1717
import time
18+
import traceback
1819
from collections import defaultdict
1920
from functools import partial
2021
from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network
21-
from typing import Optional, TYPE_CHECKING
22+
from typing import Optional, TYPE_CHECKING, List
2223
import asyncio
2324

2425
import attr
@@ -786,6 +787,11 @@ async def broadcast_transaction(self, raw_tx):
786787
self.txs_sent += 1
787788
return hex_hash
788789

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+
789795
async def limited_history(self, hashX):
790796
'''Returns a pair (history, cost).
791797
@@ -978,7 +984,7 @@ class ElectrumX(SessionBase):
978984
'''A TCP server that handles incoming Electrum connections.'''
979985

980986
PROTOCOL_MIN = (1, 4)
981-
PROTOCOL_MAX = (1, 4, 3)
987+
PROTOCOL_MAX = (1, 4, 4)
982988

983989
def __init__(self, *args, **kwargs):
984990
super().__init__(*args, **kwargs)
@@ -1468,6 +1474,36 @@ async def transaction_broadcast(self, raw_tx):
14681474
self.logger.info(f'sent tx: {hex_hash}')
14691475
return hex_hash
14701476

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+
14711507
async def transaction_get(self, tx_hash, verbose=False):
14721508
'''Return the serialized raw transaction given its hash
14731509
@@ -1555,7 +1591,8 @@ def set_request_handlers(self, ptuple):
15551591

15561592
if ptuple >= (1, 4, 2):
15571593
handlers['blockchain.scripthash.unsubscribe'] = self.scripthash_unsubscribe
1558-
1594+
if ptuple >= (1, 4, 4):
1595+
handlers['blockchain.transaction.broadcast_package'] = self.package_broadcast
15591596
self.request_handlers = handlers
15601597

15611598

tests/server/test_daemon.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ async def test_broadcast_transaction(daemon):
237237
daemon.session = ClientSessionGood(('sendrawtransaction', [raw_tx], tx_hash))
238238
assert await daemon.broadcast_transaction(raw_tx) == tx_hash
239239

240+
@pytest.mark.asyncio
241+
async def test_broadcast_package(daemon):
242+
package = ["deadbeef", "deadc0de", "facefeed"]
243+
result = {"package_msg": "success"}
244+
daemon.session = ClientSessionGood(('submitpackage', [package], result))
245+
assert await daemon.broadcast_package(package) == result
240246

241247
@pytest.mark.asyncio
242248
async def test_relayfee(daemon):

0 commit comments

Comments
 (0)