Skip to content

Commit 7195d65

Browse files
committed
Improve requesting transactions advertised via NewTransaction.
1 parent 7697c83 commit 7195d65

File tree

5 files changed

+71
-7
lines changed

5 files changed

+71
-7
lines changed

chia/_tests/core/full_node/test_full_node.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,7 @@ async def test_request_respond_transaction(
11691169
spend_bundle = wallet_a.generate_signed_transaction(uint64(100), receiver_puzzlehash, coin)
11701170
assert spend_bundle is not None
11711171
respond_transaction = fnp.RespondTransaction(spend_bundle)
1172+
peer.respond_transaction_allowance = 1
11721173
res = await full_node_1.respond_transaction(respond_transaction, peer)
11731174
assert res is None
11741175

@@ -3778,3 +3779,36 @@ async def test_register_for_coin_updates(
37783779
assert {cs.coin.name() for cs in response_data.coin_states} == set(first_coin)
37793780
for coin_id in remaining_coins:
37803781
assert coin_id not in response_data.coin_ids
3782+
3783+
3784+
@pytest.mark.anyio
3785+
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.HARD_FORK_2_0], reason="irrelevant")
3786+
async def test_respond_transaction(
3787+
one_node_one_block: tuple[FullNodeSimulator, ChiaServer, BlockTools], self_hostname: str
3788+
) -> None:
3789+
_, server, _ = one_node_one_block
3790+
sb = SpendBundle([], G2Element())
3791+
msg = make_msg(ProtocolMessageTypes.respond_transaction, bytes(fnp.RespondTransaction(sb)))
3792+
# A peer with no allowance should get disconnected
3793+
wsc, peer_id = await add_dummy_connection_wsc(server, self_hostname, 42)
3794+
await time_out_assert(5, lambda: peer_id in server.all_connections)
3795+
conn = server.all_connections[peer_id]
3796+
conn.peer_info = PeerInfo("1.3.3.7", conn.peer_info.port)
3797+
assert conn.respond_transaction_allowance == 0
3798+
await wsc.send_message(msg)
3799+
await time_out_assert(5, lambda: wsc.closed)
3800+
# A peer with allowance x can send x messages without being disconnected
3801+
wsc2, peer_id2 = await add_dummy_connection_wsc(server, self_hostname, 42)
3802+
await time_out_assert(5, lambda: peer_id2 in server.all_connections)
3803+
conn2 = server.all_connections[peer_id2]
3804+
conn2.peer_info = PeerInfo("1.2.3.4", conn2.peer_info.port)
3805+
conn2.respond_transaction_allowance = 2
3806+
await wsc2.send_message(msg)
3807+
await asyncio.sleep(1)
3808+
assert wsc2.closed is False
3809+
await wsc2.send_message(msg)
3810+
await asyncio.sleep(1)
3811+
assert wsc2.closed is False
3812+
# Now the x+1 message gets it disconnected
3813+
await wsc2.send_message(msg)
3814+
await time_out_assert(5, lambda: wsc2.closed)

chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ async def test_subscribe_for_hint(simulator_and_wallet: OldSimulatorsAndWallets,
506506
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
507507

508508
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
509+
fake_wallet_peer.respond_transaction_allowance = 1
509510
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
510511

511512
await full_node_api.process_spend_bundles(bundles=[tx])
@@ -557,6 +558,7 @@ async def test_subscribe_for_puzzle_hash_coin_hint_duplicates(
557558
ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [ph, int_to_bytes(1), ph])]
558559
},
559560
)
561+
wallet_connection.respond_transaction_allowance = 1
560562
await full_node_api.respond_transaction(RespondTransaction(tx), wallet_connection)
561563
await full_node_api.process_spend_bundles(bundles=[tx])
562564
# Query the coin states and make sure it doesn't contain duplicated entries
@@ -615,6 +617,7 @@ async def test_subscribe_for_hint_long_sync(
615617
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
616618

617619
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
620+
fake_wallet_peer.respond_transaction_allowance = 1
618621
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
619622

620623
await full_node_api.process_spend_bundles(bundles=[tx])

chia/full_node/full_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ async def on_connect(self, connection: WSChiaConnection) -> None:
939939
if synced and peak_height is not None:
940940
my_filter = self.mempool_manager.get_filter()
941941
mempool_request = full_node_protocol.RequestMempoolTransactions(my_filter)
942-
942+
connection.respond_transaction_allowance = 100
943943
msg = make_msg(ProtocolMessageTypes.request_mempool_transactions, mempool_request)
944944
await connection.send_message(msg)
945945

chia/full_node/full_node_api.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,29 @@ async def tx_request_and_timeout(full_node: FullNode, transaction_id: bytes32, t
110110
if peer_id not in full_node.server.all_connections:
111111
continue
112112
random_peer = full_node.server.all_connections[peer_id]
113-
request_tx = full_node_protocol.RequestTransaction(transaction_id)
114-
msg = make_msg(ProtocolMessageTypes.request_transaction, request_tx)
115-
await random_peer.send_message(msg)
116-
await asyncio.sleep(5)
117-
if full_node.mempool_manager.seen(transaction_id):
118-
break
113+
try:
114+
response = await random_peer.call_api(
115+
FullNodeAPI.request_transaction, full_node_protocol.RequestTransaction(transaction_id), timeout=5
116+
)
117+
except Exception:
118+
continue
119+
if response is None or not isinstance(response, full_node_protocol.RespondTransaction):
120+
continue
121+
try:
122+
full_node.transaction_queue.put(
123+
TransactionQueueEntry(
124+
transaction=response.transaction,
125+
transaction_bytes=bytes(response.transaction),
126+
spend_name=response.transaction.name(),
127+
peer=random_peer,
128+
test=False,
129+
peers_with_tx=peers_with_tx,
130+
),
131+
random_peer.peer_node_id,
132+
)
133+
except TransactionQueueFull:
134+
continue
135+
break
119136
except asyncio.CancelledError:
120137
pass
121138
finally:
@@ -314,6 +331,15 @@ async def respond_transaction(
314331
spend_name = std_hash(tx_bytes)
315332
if spend_name in self.full_node.full_node_store.pending_tx_request:
316333
self.full_node.full_node_store.pending_tx_request.pop(spend_name)
334+
else:
335+
if not test and peer.respond_transaction_allowance <= 0:
336+
self.log.warning(
337+
f"Disconnecting peer {peer.get_peer_logging()} for sending unsolicited RespondTransaction"
338+
)
339+
await peer.close()
340+
return None
341+
peer.respond_transaction_allowance -= 1
342+
317343
peers_with_tx = {}
318344
if spend_name in self.full_node.full_node_store.peers_with_tx:
319345
peers_with_tx = self.full_node.full_node_store.peers_with_tx.pop(spend_name)

chia/server/ws_connection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class WSChiaConnection:
123123
closed: bool = False
124124
connection_type: NodeType | None = None
125125
request_nonce: uint16 = uint16(0)
126+
respond_transaction_allowance: int = 0
126127
peer_capabilities: list[Capability] = field(default_factory=list)
127128
# Used by the Chia Seeder.
128129
version: str = field(default_factory=str)

0 commit comments

Comments
 (0)