Skip to content

Commit 73cb547

Browse files
authored
[CHIA-3712] simplify add_transaction() (#20063)
* simpliy add_transaction() and replace literals with the CONSENSUS_ERROR_BAN_SECONDS constant * fix tests and review comments * review comments
1 parent dbd6700 commit 73cb547

File tree

4 files changed

+82
-70
lines changed

4 files changed

+82
-70
lines changed

chia/_tests/core/full_node/test_full_node.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,10 @@ async def suppress_value_error(coro: Coroutine[Any, Any, None]) -> None:
919919

920920

921921
@pytest.mark.anyio
922+
@pytest.mark.limit_consensus_modes(
923+
allowed=[ConsensusMode.HARD_FORK_2_0, ConsensusMode.HARD_FORK_3_0],
924+
reason="We can no longer (reliably) farm blocks from before the hard fork",
925+
)
922926
async def test_new_transaction_and_mempool(
923927
wallet_nodes: tuple[
924928
FullNodeSimulator, FullNodeSimulator, ChiaServer, ChiaServer, WalletTool, WalletTool, BlockTools
@@ -946,8 +950,10 @@ async def test_new_transaction_and_mempool(
946950

947951
# Makes a bunch of coins
948952
conditions_dict: dict[ConditionOpcode, list[ConditionWithArgs]] = {ConditionOpcode.CREATE_COIN: []}
949-
# This should fit in one transaction
950-
for _ in range(100):
953+
# This should fit in one transaction. The test constants have a max block cost of 400,000,000
954+
# and the default max *transaction* cost is half that, so 200,000,000. CREATE_COIN has a cost of
955+
# 1,800,000, we create 80 coins
956+
for _ in range(80):
951957
receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
952958
puzzle_hashes.append(receiver_puzzlehash)
953959
output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [receiver_puzzlehash, int_to_bytes(10000000000)])
@@ -1046,8 +1052,8 @@ async def test_new_transaction_and_mempool(
10461052
# these numbers reflect the capacity of the mempool. In these
10471053
# tests MEMPOOL_BLOCK_BUFFER is 1. The other factors are COST_PER_BYTE
10481054
# and MAX_BLOCK_COST_CLVM
1049-
assert included_tx == 23
1050-
assert not_included_tx == 10
1055+
assert included_tx == 20
1056+
assert not_included_tx == 7
10511057
assert seen_bigger_transaction_has_high_fee
10521058

10531059
# Mempool is full

chia/_tests/core/mempool/test_mempool.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
7373
from chia.types.mempool_item import MempoolItem, UnspentLineageInfo
7474
from chia.util.casts import int_to_bytes
75-
from chia.util.errors import Err
75+
from chia.util.errors import Err, ValidationError
7676
from chia.util.hash import std_hash
7777
from chia.util.recursive_replace import recursive_replace
7878
from chia.wallet.conditions import AssertCoinAnnouncement, AssertPuzzleAnnouncement
@@ -361,7 +361,10 @@ async def respond_transaction(
361361
self.full_node.full_node_store.pending_tx_request.pop(spend_name)
362362
if spend_name in self.full_node.full_node_store.peers_with_tx:
363363
self.full_node.full_node_store.peers_with_tx.pop(spend_name)
364-
ret = await self.full_node.add_transaction(tx.transaction, spend_name, peer, test)
364+
try:
365+
ret = await self.full_node.add_transaction(tx.transaction, spend_name, peer, test)
366+
except ValidationError as e:
367+
ret = (MempoolInclusionStatus.FAILED, e.code)
365368
invariant_check_mempool(self.full_node.mempool_manager.mempool)
366369
return ret
367370

@@ -2865,8 +2868,9 @@ async def test_invalid_coin_spend_coin(
28652868
coin_spend_0 = make_spend(coin_0, cs.puzzle_reveal, cs.solution)
28662869
new_bundle = recursive_replace(spend_bundle, "coin_spends", [coin_spend_0, *spend_bundle.coin_spends[1:]])
28672870
assert spend_bundle is not None
2868-
res = await full_node_1.full_node.add_transaction(new_bundle, new_bundle.name(), test=True)
2869-
assert res == (MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH)
2871+
with pytest.raises(ValidationError) as e:
2872+
await full_node_1.full_node.add_transaction(new_bundle, new_bundle.name(), test=True)
2873+
assert e.value.code == Err.WRONG_PUZZLE_HASH
28702874

28712875

28722876
coins = make_test_coins()

chia/full_node/full_node.py

Lines changed: 55 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from chia.protocols.full_node_protocol import RequestBlocks, RespondBlock, RespondBlocks, RespondSignagePoint
6969
from chia.protocols.outbound_message import Message, NodeType, make_msg
7070
from chia.protocols.protocol_message_types import ProtocolMessageTypes
71+
from chia.protocols.protocol_timing import CONSENSUS_ERROR_BAN_SECONDS
7172
from chia.protocols.shared_protocol import Capability
7273
from chia.protocols.wallet_protocol import CoinStateUpdate, RemovedMempoolItem
7374
from chia.rpc.rpc_server import StateChangedProtocol
@@ -502,11 +503,16 @@ async def _handle_one_transaction(self, entry: TransactionQueueEntry) -> None:
502503
except asyncio.CancelledError:
503504
error_stack = traceback.format_exc()
504505
self.log.debug(f"Cancelling _handle_one_transaction, closing: {error_stack}")
506+
except ValidationError as e:
507+
self.log.exception("ValidationError in _handle_one_transaction, closing")
508+
if peer is not None:
509+
await peer.close(CONSENSUS_ERROR_BAN_SECONDS)
510+
entry.done.set((MempoolInclusionStatus.FAILED, e.code))
505511
except Exception:
506-
error_stack = traceback.format_exc()
507-
self.log.error(f"Error in _handle_one_transaction, closing: {error_stack}")
512+
self.log.exception("Error in _handle_one_transaction, closing")
508513
if peer is not None:
509-
await peer.close()
514+
await peer.close(CONSENSUS_ERROR_BAN_SECONDS)
515+
entry.done.set((MempoolInclusionStatus.FAILED, Err.UNKNOWN))
510516
finally:
511517
self.add_transaction_semaphore.release()
512518

@@ -1092,13 +1098,13 @@ async def request_validate_wp(
10921098
response = await weight_proof_peer.call_api(FullNodeAPI.request_proof_of_weight, request, timeout=wp_timeout)
10931099
# Disconnect from this peer, because they have not behaved properly
10941100
if response is None or not isinstance(response, full_node_protocol.RespondProofOfWeight):
1095-
await weight_proof_peer.close(600)
1101+
await weight_proof_peer.close(CONSENSUS_ERROR_BAN_SECONDS)
10961102
raise RuntimeError(f"Weight proof did not arrive in time from peer: {weight_proof_peer.peer_info.host}")
10971103
if response.wp.recent_chain_data[-1].reward_chain_block.height != peak_height:
1098-
await weight_proof_peer.close(600)
1104+
await weight_proof_peer.close(CONSENSUS_ERROR_BAN_SECONDS)
10991105
raise RuntimeError(f"Weight proof had the wrong height: {weight_proof_peer.peer_info.host}")
11001106
if response.wp.recent_chain_data[-1].reward_chain_block.weight != peak_weight:
1101-
await weight_proof_peer.close(600)
1107+
await weight_proof_peer.close(CONSENSUS_ERROR_BAN_SECONDS)
11021108
raise RuntimeError(f"Weight proof had the wrong weight: {weight_proof_peer.peer_info.host}")
11031109
if self.in_bad_peak_cache(response.wp):
11041110
raise ValueError("Weight proof failed bad peak cache validation")
@@ -1113,10 +1119,10 @@ async def request_validate_wp(
11131119
try:
11141120
validated, fork_point, summaries = await self.weight_proof_handler.validate_weight_proof(response.wp)
11151121
except Exception as e:
1116-
await weight_proof_peer.close(600)
1122+
await weight_proof_peer.close(CONSENSUS_ERROR_BAN_SECONDS)
11171123
raise ValueError(f"Weight proof validation threw an error {e}")
11181124
if not validated:
1119-
await weight_proof_peer.close(600)
1125+
await weight_proof_peer.close(CONSENSUS_ERROR_BAN_SECONDS)
11201126
raise ValueError("Weight proof validation failed")
11211127
self.log.info(f"Re-checked peers: total of {len(peers_with_peak)} peers with peak {peak_height}")
11221128
self.sync_store.set_sync_mode(True)
@@ -1378,7 +1384,7 @@ async def ingest_blocks(
13781384
vs,
13791385
)
13801386
if err is not None:
1381-
await peer.close(600)
1387+
await peer.close(CONSENSUS_ERROR_BAN_SECONDS)
13821388
raise ValueError(f"Failed to validate block batch {start_height} to {end_height}: {err}")
13831389
if end_height - block_rate_height > 100:
13841390
now = time.monotonic()
@@ -2767,66 +2773,56 @@ async def add_transaction(
27672773
return MempoolInclusionStatus.SUCCESS, None
27682774
if self.mempool_manager.seen(spend_name):
27692775
return MempoolInclusionStatus.FAILED, Err.ALREADY_INCLUDING_TRANSACTION
2770-
self.mempool_manager.add_and_maybe_pop_seen(spend_name)
27712776
self.log.debug(f"Processing transaction: {spend_name}")
27722777
# Ignore if syncing or if we have not yet received a block
27732778
# the mempool must have a peak to validate transactions
27742779
if self.sync_store.get_sync_mode() or self.mempool_manager.peak is None:
2775-
status = MempoolInclusionStatus.FAILED
2776-
error: Optional[Err] = Err.NO_TRANSACTIONS_WHILE_SYNCING
2777-
self.mempool_manager.remove_seen(spend_name)
2778-
else:
2780+
return MempoolInclusionStatus.FAILED, Err.NO_TRANSACTIONS_WHILE_SYNCING
2781+
2782+
cost_result = await self.mempool_manager.pre_validate_spendbundle(transaction, spend_name, self._bls_cache)
2783+
2784+
self.mempool_manager.add_and_maybe_pop_seen(spend_name)
2785+
2786+
if self.config.get("log_mempool", False): # pragma: no cover
27792787
try:
2780-
cost_result = await self.mempool_manager.pre_validate_spendbundle(
2781-
transaction, spend_name, self._bls_cache
2782-
)
2783-
except ValidationError as e:
2784-
self.mempool_manager.remove_seen(spend_name)
2785-
return MempoolInclusionStatus.FAILED, e.code
2788+
mempool_dir = path_from_root(self.root_path, "mempool-log") / f"{self.blockchain.get_peak_height()}"
2789+
mempool_dir.mkdir(parents=True, exist_ok=True)
2790+
with open(mempool_dir / f"{spend_name}.bundle", "wb+") as f:
2791+
f.write(bytes(transaction))
27862792
except Exception:
2787-
self.mempool_manager.remove_seen(spend_name)
2788-
raise
2793+
self.log.exception(f"Failed to log mempool item: {spend_name}")
27892794

2790-
if self.config.get("log_mempool", False): # pragma: no cover
2791-
try:
2792-
mempool_dir = path_from_root(self.root_path, "mempool-log") / f"{self.blockchain.get_peak_height()}"
2793-
mempool_dir.mkdir(parents=True, exist_ok=True)
2794-
with open(mempool_dir / f"{spend_name}.bundle", "wb+") as f:
2795-
f.write(bytes(transaction))
2796-
except Exception:
2797-
self.log.exception(f"Failed to log mempool item: {spend_name}")
2798-
2799-
async with self.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.low):
2800-
if self.mempool_manager.get_spendbundle(spend_name) is not None:
2801-
self.mempool_manager.remove_seen(spend_name)
2802-
return MempoolInclusionStatus.SUCCESS, None
2803-
if self.mempool_manager.peak is None:
2804-
return MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED
2805-
info = await self.mempool_manager.add_spend_bundle(
2806-
transaction, cost_result, spend_name, self.mempool_manager.peak.height
2807-
)
2808-
status = info.status
2809-
error = info.error
2810-
if status == MempoolInclusionStatus.SUCCESS:
2811-
self.log.debug(
2812-
f"Added transaction to mempool: {spend_name} mempool size: "
2813-
f"{self.mempool_manager.mempool.total_mempool_cost()} normalized "
2814-
f"{self.mempool_manager.mempool.total_mempool_cost() / 5000000}"
2815-
)
2795+
async with self.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.low):
2796+
if self.mempool_manager.get_spendbundle(spend_name) is not None:
2797+
self.mempool_manager.remove_seen(spend_name)
2798+
return MempoolInclusionStatus.SUCCESS, None
2799+
if self.mempool_manager.peak is None:
2800+
return MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED
2801+
info = await self.mempool_manager.add_spend_bundle(
2802+
transaction, cost_result, spend_name, self.mempool_manager.peak.height
2803+
)
2804+
status = info.status
2805+
error = info.error
2806+
if status == MempoolInclusionStatus.SUCCESS:
2807+
self.log.debug(
2808+
f"Added transaction to mempool: {spend_name} mempool size: "
2809+
f"{self.mempool_manager.mempool.total_mempool_cost()} normalized "
2810+
f"{self.mempool_manager.mempool.total_mempool_cost() / 5000000}"
2811+
)
28162812

2817-
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
2818-
# vector.
2819-
mempool_item = self.mempool_manager.get_mempool_item(spend_name)
2820-
assert mempool_item is not None
2821-
await self.broadcast_removed_tx(info.removals)
2822-
await self.broadcast_added_tx(mempool_item, current_peer=peer)
2813+
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
2814+
# vector.
2815+
mempool_item = self.mempool_manager.get_mempool_item(spend_name)
2816+
assert mempool_item is not None
2817+
await self.broadcast_removed_tx(info.removals)
2818+
await self.broadcast_added_tx(mempool_item, current_peer=peer)
28232819

2824-
if self.simulator_transaction_callback is not None: # callback
2825-
await self.simulator_transaction_callback(spend_name)
2820+
if self.simulator_transaction_callback is not None: # callback
2821+
await self.simulator_transaction_callback(spend_name)
28262822

2827-
else:
2828-
self.mempool_manager.remove_seen(spend_name)
2829-
self.log.debug(f"Wasn't able to add transaction with id {spend_name}, status {status} error: {error}")
2823+
else:
2824+
self.mempool_manager.remove_seen(spend_name)
2825+
self.log.debug(f"Wasn't able to add transaction with id {spend_name}, status {status} error: {error}")
28302826
return status, error
28312827

28322828
async def broadcast_added_tx(

chia/server/server.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,15 @@ async def connection_closed(
553553
# in this case we still want to do the banning logic and remove the connection from the list
554554
# but the other cleanup should already have been done so we skip that
555555

556-
if is_localhost(connection.peer_info.host) and ban_time != 0:
557-
self.log.warning(f"Trying to ban localhost for {ban_time}, but will not ban")
558-
ban_time = 0
556+
if ban_time > 0:
557+
if is_localhost(connection.peer_info.host):
558+
self.log.warning(f"Trying to ban localhost for {ban_time}, but will not ban")
559+
ban_time = 0
560+
elif self.is_trusted_peer(connection, self.config.get("trusted_peers", {})):
561+
self.log.warning(
562+
f"Trying to ban trusted peer {connection.peer_info.host} for {ban_time}, but will not ban"
563+
)
564+
ban_time = 0
559565
if ban_time > 0:
560566
ban_until: float = time.time() + ban_time
561567
self.log.warning(f"Banning {connection.peer_info.host} for {ban_time} seconds")

0 commit comments

Comments
 (0)