Skip to content

Commit 411ade2

Browse files
committed
Improve requesting transactions advertised via NewTransaction.
1 parent 7697c83 commit 411ade2

File tree

7 files changed

+81
-30
lines changed

7 files changed

+81
-30
lines changed

chia/_tests/core/full_node/test_full_node.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -990,9 +990,6 @@ async def test_new_transaction_and_mempool(
990990
await full_node_1.new_transaction(new_transaction, fake_peer)
991991
await time_out_assert(10, new_transaction_requested, True, incoming_queue, new_transaction)
992992

993-
respond_transaction_2 = fnp.RespondTransaction(spend_bundle)
994-
await full_node_1.respond_transaction(respond_transaction_2, peer)
995-
996993
blocks = bt.get_consecutive_blocks(
997994
1,
998995
block_list_input=blocks,
@@ -1039,9 +1036,8 @@ async def test_new_transaction_and_mempool(
10391036
]
10401037
spend_bundle = WalletSpendBundle.aggregate(spend_bundles)
10411038
assert estimate_fees(spend_bundle) == fee
1042-
respond_transaction = wallet_protocol.SendTransaction(spend_bundle)
10431039

1044-
await full_node_1.send_transaction(respond_transaction, fake_peer)
1040+
await full_node_1.send_transaction(SendTransaction(spend_bundle), fake_peer)
10451041

10461042
request = fnp.RequestTransaction(spend_bundle.get_hash())
10471043
req = await full_node_1.request_transaction(request)
@@ -1168,9 +1164,8 @@ async def test_request_respond_transaction(
11681164
coin = find_reward_coin(blocks[-1], wallet_ph)
11691165
spend_bundle = wallet_a.generate_signed_transaction(uint64(100), receiver_puzzlehash, coin)
11701166
assert spend_bundle is not None
1171-
respond_transaction = fnp.RespondTransaction(spend_bundle)
1172-
res = await full_node_1.respond_transaction(respond_transaction, peer)
1173-
assert res is None
1167+
res = await full_node_1.send_transaction(SendTransaction(spend_bundle), peer)
1168+
assert res is not None
11741169

11751170
# Check broadcast
11761171
await time_out_assert(10, time_out_messages(incoming_queue, "new_transaction"))
@@ -1182,7 +1177,7 @@ async def test_request_respond_transaction(
11821177

11831178

11841179
@pytest.mark.anyio
1185-
async def test_respond_transaction_fail(
1180+
async def test_send_transaction_fail(
11861181
wallet_nodes: tuple[
11871182
FullNodeSimulator, FullNodeSimulator, ChiaServer, ChiaServer, WalletTool, WalletTool, BlockTools
11881183
],
@@ -1223,9 +1218,11 @@ async def test_respond_transaction_fail(
12231218
spend_bundle = wallet_a.generate_signed_transaction(uint64(100_000_000_000_000), receiver_puzzlehash, coin)
12241219

12251220
assert spend_bundle is not None
1226-
respond_transaction = fnp.RespondTransaction(spend_bundle)
1227-
msg = await full_node_1.respond_transaction(respond_transaction, peer)
1228-
assert msg is None
1221+
response_msg = await full_node_1.send_transaction(SendTransaction(spend_bundle), peer)
1222+
assert response_msg is not None
1223+
assert TransactionAck.from_bytes(response_msg.data) == TransactionAck(
1224+
txid=spend_bundle.name(), status=uint8(MempoolInclusionStatus.FAILED), error="MINTING_COIN"
1225+
)
12291226

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

chia/_tests/core/full_node/test_performance.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ async def test_full_block_performance(
7373
)
7474
assert spend_bundle is not None
7575

76-
respond_transaction_2 = fnp.RespondTransaction(spend_bundle)
77-
await full_node_1.respond_transaction(respond_transaction_2, fake_peer)
76+
await full_node_1.full_node.add_transaction(spend_bundle, spend_bundle.name(), fake_peer)
7877

7978
blocks = bt.get_consecutive_blocks(
8079
1,
@@ -107,9 +106,7 @@ async def test_full_block_performance(
107106
num_tx: int = 0
108107
for spend_bundle, spend_bundle_id in zip(spend_bundles, spend_bundle_ids):
109108
num_tx += 1
110-
respond_transaction = fnp.RespondTransaction(spend_bundle)
111-
112-
await full_node_1.respond_transaction(respond_transaction, fake_peer)
109+
await full_node_1.full_node.add_transaction(spend_bundle, spend_bundle_id, fake_peer)
113110

114111
request_transaction = fnp.RequestTransaction(spend_bundle_id)
115112
req = await full_node_1.request_transaction(request_transaction)

chia/_tests/core/mempool/test_mempool.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,7 @@ async def test_basic_mempool_manager(
425425
coin = await next_block(full_node_1, wallet_a, bt)
426426
spend_bundle = generate_test_spend_bundle(wallet_a, coin)
427427
assert spend_bundle is not None
428-
tx: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(spend_bundle)
429-
await full_node_1.respond_transaction(tx, peer, test=True)
428+
await full_node_1.send_transaction(wallet_protocol.SendTransaction(spend_bundle), peer, test=True)
430429

431430
await time_out_assert(
432431
10,

chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from chia._tests.util.time_out_assert import time_out_assert
1414
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
1515
from chia.protocols import wallet_protocol
16-
from chia.protocols.full_node_protocol import RespondTransaction
1716
from chia.protocols.outbound_message import Message, NodeType
1817
from chia.protocols.protocol_message_types import ProtocolMessageTypes
1918
from chia.protocols.wallet_protocol import CoinStateUpdate, RespondToCoinUpdates
@@ -506,7 +505,7 @@ async def test_subscribe_for_hint(simulator_and_wallet: OldSimulatorsAndWallets,
506505
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
507506

508507
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
509-
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
508+
await full_node_api.send_transaction(wallet_protocol.SendTransaction(tx), fake_wallet_peer)
510509

511510
await full_node_api.process_spend_bundles(bundles=[tx])
512511

@@ -557,7 +556,7 @@ async def test_subscribe_for_puzzle_hash_coin_hint_duplicates(
557556
ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [ph, int_to_bytes(1), ph])]
558557
},
559558
)
560-
await full_node_api.respond_transaction(RespondTransaction(tx), wallet_connection)
559+
await full_node_api.send_transaction(wallet_protocol.SendTransaction(tx), wallet_connection)
561560
await full_node_api.process_spend_bundles(bundles=[tx])
562561
# Query the coin states and make sure it doesn't contain duplicated entries
563562
msg = wallet_protocol.RegisterForPhUpdates([ph], uint32(0))
@@ -615,7 +614,7 @@ async def test_subscribe_for_hint_long_sync(
615614
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
616615

617616
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
618-
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
617+
await full_node_api.send_transaction(wallet_protocol.SendTransaction(tx), fake_wallet_peer)
619618

620619
await full_node_api.process_spend_bundles(bundles=[tx])
621620

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: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,26 @@ 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+
response = await random_peer.call_api(
114+
FullNodeAPI.request_transaction, full_node_protocol.RequestTransaction(transaction_id), timeout=5
115+
)
116+
if response is None or not isinstance(response, full_node_protocol.RespondTransaction):
117+
continue
118+
try:
119+
full_node.transaction_queue.put(
120+
TransactionQueueEntry(
121+
transaction=response.transaction,
122+
transaction_bytes=bytes(response.transaction),
123+
spend_name=response.transaction.name(),
124+
peer=random_peer,
125+
test=False,
126+
peers_with_tx=peers_with_tx,
127+
),
128+
random_peer.peer_node_id,
129+
)
130+
except TransactionQueueFull:
131+
continue
132+
break
119133
except asyncio.CancelledError:
120134
pass
121135
finally:
@@ -314,6 +328,13 @@ async def respond_transaction(
314328
spend_name = std_hash(tx_bytes)
315329
if spend_name in self.full_node.full_node_store.pending_tx_request:
316330
self.full_node.full_node_store.pending_tx_request.pop(spend_name)
331+
else:
332+
if not test and peer.respond_transaction_allowance <= 0:
333+
self.log.warning(f"Banning peer {peer.get_peer_logging()} for sending unsolicited RespondTransaction")
334+
await peer.close(CONSENSUS_ERROR_BAN_SECONDS)
335+
return None
336+
peer.respond_transaction_allowance -= 1
337+
317338
peers_with_tx = {}
318339
if spend_name in self.full_node.full_node_store.peers_with_tx:
319340
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)