Skip to content

Commit 0414f7f

Browse files
committed
Merge branch 'main' into 4503-encapsulate-enable-tx-pool-flat-in-transaction-service
# Conflicts: # packages/relay/src/lib/services/ethService/transactionService/TransactionService.ts # packages/relay/src/lib/services/transactionPoolService/transactionPoolService.ts
2 parents d254e31 + 4ea1929 commit 0414f7f

File tree

16 files changed

+660
-174
lines changed

16 files changed

+660
-174
lines changed

.env.http.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ OPERATOR_KEY_MAIN= # Operator private key used to sign transaction
5858
# TIER_3_RATE_LIMIT=1600 # Max request limit for static return endpoints
5959
# LIMIT_DURATION=60000 # Duration in ms for rate limits
6060

61+
# ========== TRANSACTION POOL ========
62+
# PENDING_TRANSACTION_STORAGE_TTL=30 # Time-to-live (TTL) in seconds for transaction payloads stored in Redis
63+
6164
# ========== HBAR RATE LIMITING ==========
6265
# HBAR_RATE_LIMIT_TINYBAR=25000000000 # Total HBAR budget (250 HBARs)
6366
# HBAR_RATE_LIMIT_DURATION=86400000 # HBAR budget limit duration (1 day)

.env.ws.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ SUBSCRIPTIONS_ENABLED=true # Must be true for the WebSocket server to func
5656
# REDIS_RECONNECT_DELAY_MS=1000 # Delay between reconnect retries
5757
# MULTI_SET=false # Implementation for setting multiple K/V pairs
5858

59+
# ========== TRANSACTION POOL ========
60+
# PENDING_TRANSACTION_STORAGE_TTL=30 # Time-to-live (TTL) in seconds for transaction payloads stored in Redis
61+
5962
# ========== OTHER SETTINGS ==========
6063
# CLIENT_TRANSPORT_SECURITY=false # Enable or disable TLS for both networks
6164
# USE_ASYNC_TX_PROCESSING=true # If true, returns tx hash immediately after prechecks

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ Unless you need to set a non-default value, it is recommended to only populate o
9090
| `OPCODELOGGER_ENABLED` | "true" | Whether the `opcodeLogger` tracer is enabled for a call to `debug_traceTransaction`. This setting should match the value `hiero.mirror.web3.opcode.tracer.enabled` in the Mirror Node used. See [HIP-801](https://hips.hedera.com/hip/hip-801) for more information. |
9191
| `PAYMASTER_ENABLED` | "false" | Flag to enable or disable the Paymaster functionally |
9292
| `PAYMASTER_WHITELIST` | "[]" | List of "to" addresses that will have fully subsidized transactions if the gasPrice was set to 0 by the signer |
93-
| `PENDING_TRANSACTION_STORAGE_TTL` | 30 | Sets the ttl for the pending transactions in the transaction pool
9493
| `READ_ONLY` | "false" | Starts the JSON-RPC Relay in Read-Only mode. In Read-Only mode, write operations, _e.g._, `eth_sendRawTransaction` or query operations to the Consensus Node will trigger an `Unsupported operation` error. |
9594
| `REDIS_ENABLED` | "true" | Enable usage of Redis as shared cache |
9695
| `REDIS_RECONNECT_DELAY_MS` | "1000" | Sets the delay between reconnect retries from the Redis client in ms |
@@ -102,6 +101,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
102101
| `TIER_1_RATE_LIMIT` | "100" | Maximum restrictive request count limit used for expensive endpoints rate limiting. |
103102
| `TIER_2_RATE_LIMIT` | "800" | Maximum moderate request count limit used for non expensive endpoints. |
104103
| `TIER_3_RATE_LIMIT` | "1600" | Maximum relaxed request count limit used for static return endpoints. |
104+
| `PENDING_TRANSACTION_STORAGE_TTL` | "30" | Time-to-live (TTL) in seconds for transaction payloads stored in Redis. After this period, transaction data expires and is automatically cleaned up by Redis and the Lua-based orphan cleanup mechanism. |
105105
| `TX_DEFAULT_GAS` | "400000" | Default gas for transactions that do not specify gas. |
106106
| `USE_ASYNC_TX_PROCESSING` | "true" | Set to `true` to enable `eth_sendRawTransaction` to return the transaction hash immediately after passing all prechecks, while processing the transaction asynchronously in the background. |
107107
| `USE_MIRROR_NODE_MODULARIZED_SERVICES` | null | Controls routing of Mirror Node traffic through modularized services. When set to `true`, enables routing a percentage of traffic to modularized services. When set to `false`, ensures traffic follows the traditional non-modularized flow. When not set (i.e. `null` by default), no specific routing preference is applied. As Mirror Node gradually transitions to a fully modularized architecture across all networks, this setting will eventually default to `true`. |

packages/config-service/src/services/globalConfig.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,6 @@ const _CONFIG = {
519519
required: false,
520520
defaultValue: [],
521521
},
522-
PENDING_TRANSACTION_STORAGE_TTL: {
523-
type: 'number',
524-
required: false,
525-
defaultValue: 30,
526-
},
527522
RATE_LIMIT_DISABLED: {
528523
type: 'boolean',
529524
required: false,
@@ -614,6 +609,11 @@ const _CONFIG = {
614609
required: false,
615610
defaultValue: false,
616611
},
612+
PENDING_TRANSACTION_STORAGE_TTL: {
613+
type: 'number',
614+
required: false,
615+
defaultValue: 30,
616+
},
617617
TIER_1_RATE_LIMIT: {
618618
type: 'number',
619619
required: false,

packages/relay/src/lib/relay.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ import HAPIService from './services/hapiService/hapiService';
2525
import { HbarLimitService } from './services/hbarLimitService';
2626
import MetricService from './services/metricService/metricService';
2727
import { registerRpcMethods } from './services/registryService/rpcMethodRegistryService';
28-
import { LocalPendingTransactionStorage } from './services/transactionPoolService/LocalPendingTransactionStorage';
29-
import { RedisPendingTransactionStorage } from './services/transactionPoolService/RedisPendingTransactionStorage';
28+
import { PendingTransactionStorageFactory } from './services/transactionPoolService/PendingTransactionStorageFactory';
3029
import {
3130
IEthExecutionEventPayload,
3231
IExecuteQueryEventPayload,
@@ -327,9 +326,7 @@ export class Relay {
327326
: this.mirrorNodeClient;
328327
this.metricService = new MetricService(this.logger, metricsCollector, this.register, hbarLimitService);
329328

330-
const storage = this.redisClient
331-
? new RedisPendingTransactionStorage(this.redisClient)
332-
: new LocalPendingTransactionStorage();
329+
const storage = PendingTransactionStorageFactory.create(this.redisClient);
333330

334331
// Create Eth implementation with connected Redis client
335332
this.ethImpl = new EthImpl(

packages/relay/src/lib/services/ethService/transactionService/TransactionService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ export class TransactionService implements ITransactionService {
496496
);
497497

498498
// Remove the transaction from the transaction pool after submission
499-
await this.transactionPoolService.removeTransaction(originalCallerAddress, parsedTx.hash!);
499+
await this.transactionPoolService.removeTransaction(originalCallerAddress, parsedTx.serialized);
500500

501501
sendRawTransactionError = error;
502502

packages/relay/src/lib/services/transactionPoolService/LocalPendingTransactionStorage.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ import { PendingTransactionStorage } from '../../types/transactionPool';
1010
* atomicity across multiple process instances.
1111
*/
1212
export class LocalPendingTransactionStorage implements PendingTransactionStorage {
13-
// Maps address to a Set of transaction hashes for that address
13+
// Maps address to a Set of RLP hex payloads for that address
1414
private readonly pendingTransactions: Map<string, Set<string>>;
1515

16+
// Global set of all pending RLP hex payloads
17+
private readonly globalTransactionIndex: Set<string>;
18+
1619
constructor() {
1720
this.pendingTransactions = new Map();
21+
this.globalTransactionIndex = new Set();
1822
}
1923

2024
/**
@@ -30,37 +34,44 @@ export class LocalPendingTransactionStorage implements PendingTransactionStorage
3034

3135
/**
3236
* Adds a pending transaction entry for the given address.
37+
* Atomically indexes the transaction (per-address + global) and persists its payload.
3338
*
3439
* @param addr - The account address
35-
* @param txHash - The transaction hash to add to the pending list
40+
* @param rlpHex - The RLP-encoded transaction as a hex string
3641
*/
37-
async addToList(addr: string, txHash: string): Promise<void> {
42+
async addToList(addr: string, rlpHex: string): Promise<void> {
3843
// Initialize the set if it doesn't exist
3944
if (!this.pendingTransactions.has(addr)) {
4045
this.pendingTransactions.set(addr, new Set());
4146
}
4247

4348
const addressTransactions = this.pendingTransactions.get(addr)!;
44-
addressTransactions.add(txHash);
49+
addressTransactions.add(rlpHex);
50+
51+
// Add to global index
52+
this.globalTransactionIndex.add(rlpHex);
4553
}
4654

4755
/**
4856
* Removes a transaction from the pending list of the given address.
4957
*
5058
* @param address - The account address whose transaction should be removed
51-
* @param txHash - The transaction hash to remove
59+
* @param rlpHex - The RLP-encoded transaction as a hex string
5260
*/
53-
async removeFromList(address: string, txHash: string): Promise<void> {
61+
async removeFromList(address: string, rlpHex: string): Promise<void> {
5462
const addressTransactions = this.pendingTransactions.get(address);
5563

5664
if (addressTransactions) {
57-
addressTransactions.delete(txHash);
65+
addressTransactions.delete(rlpHex);
5866

5967
// Clean up empty sets to prevent memory leaks
6068
if (addressTransactions.size === 0) {
6169
this.pendingTransactions.delete(address);
6270
}
6371
}
72+
73+
// Remove from global index
74+
this.globalTransactionIndex.delete(rlpHex);
6475
}
6576

6677
/**
@@ -70,5 +81,26 @@ export class LocalPendingTransactionStorage implements PendingTransactionStorage
7081
*/
7182
async removeAll(): Promise<void> {
7283
this.pendingTransactions.clear();
84+
this.globalTransactionIndex.clear();
85+
}
86+
87+
/**
88+
* Retrieves all pending transaction payloads (RLP hex) across all addresses.
89+
*
90+
* @returns Set of all pending transaction RLP hex strings
91+
*/
92+
async getAllTransactionPayloads(): Promise<Set<string>> {
93+
return this.globalTransactionIndex;
94+
}
95+
96+
/**
97+
* Retrieves pending transaction payloads (RLP hex) for a specific address.
98+
*
99+
* @param address - The account address to query
100+
* @returns Set of transaction RLP hex strings for the address
101+
*/
102+
async getTransactionPayloads(address: string): Promise<Set<string>> {
103+
const addressTransactions = this.pendingTransactions.get(address);
104+
return addressTransactions ?? new Set();
73105
}
74106
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { RedisClientType } from 'redis';
4+
5+
import { PendingTransactionStorage } from '../../types/transactionPool';
6+
import { LocalPendingTransactionStorage } from './LocalPendingTransactionStorage';
7+
import { RedisPendingTransactionStorage } from './RedisPendingTransactionStorage';
8+
9+
/**
10+
* Factory for creating PendingTransactionStorage instances.
11+
*
12+
* Encapsulates the logic for selecting the appropriate storage implementation
13+
* based on available infrastructure (Redis vs in-memory).
14+
*/
15+
export class PendingTransactionStorageFactory {
16+
/**
17+
* Creates a PendingTransactionStorage instance.
18+
*
19+
* @param redisClient - Optional Redis client. If provided, creates Redis-backed storage;
20+
* otherwise creates local in-memory storage.
21+
* @returns A PendingTransactionStorage implementation.
22+
*/
23+
static create(redisClient?: RedisClientType): PendingTransactionStorage {
24+
return redisClient ? new RedisPendingTransactionStorage(redisClient) : new LocalPendingTransactionStorage();
25+
}
26+
}

0 commit comments

Comments
 (0)