Skip to content

Commit 840a110

Browse files
authored
feat: adds LocalPendingTransactionStorage implementation (#4408)
Signed-off-by: Simeon Nakov <simeon.nakov@limechain.tech>
1 parent ba268d5 commit 840a110

File tree

4 files changed

+556
-3
lines changed

4 files changed

+556
-3
lines changed

packages/relay/src/lib/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export * from '../types/rateLimiter';
1515
export * from './rateLimiterService/LruRateLimitStore';
1616
export * from './rateLimiterService/RedisRateLimitStore';
1717
export * from './rateLimiterService/rateLimiterService';
18+
export * from './transactionPoolService/LocalPendingTransactionStorage';
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { AddToListResult, PendingTransactionStorage } from '../../types/transactionPool';
4+
5+
/**
6+
* Local in-memory implementation of PendingTransactionStorage.
7+
* Uses Map-based storage to track pending transactions without external dependencies.
8+
*
9+
* This implementation is thread-safe within a single process but does not provide
10+
* atomicity across multiple process instances.
11+
*/
12+
export class LocalPendingTransactionStorage implements PendingTransactionStorage {
13+
// Maps address to a Set of transaction hashes for that address
14+
private readonly pendingTransactions: Map<string, Set<string>>;
15+
16+
// Maps transaction hash to RLP-encoded transaction data (for future use)
17+
private readonly transactionData: Map<string, string>;
18+
19+
constructor() {
20+
this.pendingTransactions = new Map();
21+
this.transactionData = new Map();
22+
}
23+
24+
/**
25+
* Retrieves the number of pending transactions for a given address.
26+
*
27+
* @param addr - The account address to query
28+
* @returns Promise resolving to the number of pending transactions
29+
*/
30+
async getList(addr: string): Promise<number> {
31+
const addressTransactions = this.pendingTransactions.get(addr);
32+
return addressTransactions ? addressTransactions.size : 0;
33+
}
34+
35+
/**
36+
* Attempts to add a pending transaction entry for the given address.
37+
*
38+
* This implementation checks that the current pending count matches the expected count
39+
* before adding a new entry, providing optimistic concurrency control.
40+
*
41+
* @param addr - The account address
42+
* @param txHash - The transaction hash to add to the pending list
43+
* @param expectedPending - The expected number of pending transactions before addition
44+
* @returns Promise resolving to AddToListResult indicating success or failure
45+
*/
46+
async addToList(addr: string, txHash: string, expectedPending: number): Promise<AddToListResult> {
47+
const currentCount = await this.getList(addr);
48+
49+
// Check if the current count matches expectations (optimistic concurrency control)
50+
if (currentCount !== expectedPending) {
51+
return { ok: false, current: currentCount };
52+
}
53+
54+
// Initialize the set if it doesn't exist
55+
if (!this.pendingTransactions.has(addr)) {
56+
this.pendingTransactions.set(addr, new Set());
57+
}
58+
59+
const addressTransactions = this.pendingTransactions.get(addr)!;
60+
addressTransactions.add(txHash);
61+
62+
return { ok: true, newValue: addressTransactions.size };
63+
}
64+
65+
/**
66+
* Removes a transaction from the pending list of the given address.
67+
*
68+
* @param address - The account address whose transaction should be removed
69+
* @param txHash - The transaction hash to remove
70+
* @returns Promise resolving to the updated pending count
71+
*/
72+
async removeFromList(address: string, txHash: string): Promise<number> {
73+
const addressTransactions = this.pendingTransactions.get(address);
74+
75+
if (addressTransactions) {
76+
addressTransactions.delete(txHash);
77+
78+
// Clean up empty sets to prevent memory leaks
79+
if (addressTransactions.size === 0) {
80+
this.pendingTransactions.delete(address);
81+
}
82+
}
83+
84+
// Also remove from transaction data map
85+
this.transactionData.delete(txHash);
86+
87+
return addressTransactions ? addressTransactions.size : 0;
88+
}
89+
90+
/**
91+
* Removes all pending transactions across all addresses.
92+
*
93+
* @returns Promise that resolves once all entries have been cleared
94+
*/
95+
async removeAll(): Promise<void> {
96+
this.pendingTransactions.clear();
97+
this.transactionData.clear();
98+
}
99+
}

packages/relay/src/lib/types/transactionPool.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,20 @@ export interface PendingTransactionStorage {
6363
* Attempts to add a pending transaction entry for the given address.
6464
*
6565
* @param addr - The account address.
66+
* @param txHash - The transaction hash to add to the pending list.
6667
* @param expectedPending - The expected number of pending transactions.
6768
* @returns A promise that resolves to an {@link AddToListResult}.
6869
*/
69-
addToList(addr: string, expectedPending: number): Promise<AddToListResult>;
70+
addToList(addr: string, txHash: string, expectedPending: number): Promise<AddToListResult>;
7071

7172
/**
7273
* Removes a transaction from the pending list of the given address.
7374
*
7475
* @param address - The account address whose transaction should be removed.
75-
* @param transaction - The transaction identifier (e.g., hash).
76+
* @param txHash - The transaction hash to remove.
7677
* @returns A promise that resolves to the updated pending count.
7778
*/
78-
removeFromList(address: string, transaction: string): Promise<number>;
79+
removeFromList(address: string, txHash: string): Promise<number>;
7980

8081
/**
8182
* Removes all pending transactions across all addresses.

0 commit comments

Comments
 (0)