Skip to content

Conversation

@konstantinabl
Copy link
Contributor

@konstantinabl konstantinabl commented Nov 19, 2025

Description

This PR integrates a new LockService into eth_sendRawTransaction to ensure proper nonce ordering for transactions from the same sender address. The service uses a per-address locking mechanism that prevents concurrent execution of transactions from the same account, thereby avoiding nonce-related race conditions.

  • Modified sendRawTransaction to acquire a lock on the sender address before any async operations or side effects
  • Lock is released in finally block to ensure cleanup on success or failure
  • Feature controlled by USE_LOCK_SERVICE configuration flag

Related issue(s)

Fixes #4595

Testing

  • Unit tests for sendRawTransaction with lock acquisition/release scenarios
  • Unit tests for executeTransaction flow
  • Tests cover success cases, error handling, and lock cleanup

Changes from original design (optional)

N/A

Additional work needed (optional)

N/A

Checklist

  • I've assigned an assignee to this PR and related issue(s) (if applicable)
  • I've assigned a label to this PR and related issue(s) (if applicable)
  • I've assigned a milestone to this PR and related issue(s) (if applicable)
  • I've updated documentation (code comments, README, etc. if applicable)
  • I've done sufficient testing (unit, integration, etc.)

@konstantinabl konstantinabl requested review from a team as code owners November 19, 2025 11:26
@konstantinabl konstantinabl linked an issue Nov 19, 2025 that may be closed by this pull request
@konstantinabl konstantinabl self-assigned this Nov 19, 2025
@konstantinabl konstantinabl added the enhancement New feature or request label Nov 19, 2025
@konstantinabl konstantinabl added this to the 0.74.0 milestone Nov 19, 2025
@github-actions
Copy link

github-actions bot commented Nov 19, 2025

Test Results

 20 files  +  7  272 suites  +104   22m 19s ⏱️ + 7m 50s
791 tests +251  787 ✅ +253  4 💤 ±0  0 ❌  - 2 
807 runs  +251  803 ✅ +253  4 💤 ±0  0 ❌  - 2 

Results for commit 3e754ec. ± Comparison against base commit e1a74b9.

♻️ This comment has been updated with latest results.

@konstantinabl konstantinabl force-pushed the 4595-integrate-lockservice-in-ethsendraw branch from 0aa4e24 to 8b8bd82 Compare November 19, 2025 13:13
@konstantinabl konstantinabl changed the base branch from main to feat/nonce-ordering-with-locks November 19, 2025 14:36
@codecov
Copy link

codecov bot commented Nov 19, 2025

Codecov Report

❌ Patch coverage is 77.39726% with 66 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../src/lib/services/lockService/LocalLockStrategy.ts 57.41% 66 Missing ⚠️
@@                        Coverage Diff                        @@
##             feat/nonce-ordering-with-locks    #4627   +/-   ##
=================================================================
  Coverage                                  ?   94.23%           
=================================================================
  Files                                     ?      133           
  Lines                                     ?    21271           
  Branches                                  ?     1726           
=================================================================
  Hits                                      ?    20044           
  Misses                                    ?     1206           
  Partials                                  ?       21           
Flag Coverage Δ
config-service 98.85% <100.00%> (?)
relay 90.71% <75.09%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ckages/config-service/src/services/globalConfig.ts 100.00% <100.00%> (ø)
packages/relay/src/lib/clients/sdkClient.ts 96.01% <100.00%> (ø)
packages/relay/src/lib/eth.ts 99.57% <100.00%> (ø)
packages/relay/src/lib/relay.ts 98.65% <100.00%> (ø)
...thService/transactionService/TransactionService.ts 99.46% <100.00%> (ø)
.../relay/src/lib/services/hapiService/hapiService.ts 97.26% <100.00%> (ø)
...rc/lib/services/lockService/LockStrategyFactory.ts 100.00% <100.00%> (ø)
.../src/lib/services/lockService/LocalLockStrategy.ts 57.41% <57.41%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@konstantinabl konstantinabl changed the title feat: integrates lockservice in ethsendraw feat: integrates new lockService in sendRawTransaction Nov 19, 2025
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
@konstantinabl konstantinabl force-pushed the 4595-integrate-lockservice-in-ethsendraw branch from 3e754ec to 204f50b Compare November 19, 2025 20:55
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
Signed-off-by: Konstantina Blazhukova <[email protected]>
if (lockSessionKey) {
//try/catch essential for not masking errors
try {
await this.lockService.releaseLock(originalCallerAddress, lockSessionKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Releasing the lock here aligns perfectly with the design doc and works correctly, but it looks like it'd require changing method signatures, even their constructors, across multiple classes, HapiService, SdkClient methods, etc.

Instead, we can simply release the lock in the TransactionService.sendRawTransactionProcessor() method, like this:

// in TransactionService.sendRawTransactionProcessor()
...

const { txSubmitted, submittedTransactionId, error } = await this.submitTransaction(
  transactionBuffer,
  originalCallerAddress,
  networkGasPriceInWeiBars,
  requestDetails,
  lockSessionKey, // remove this
);

await this.lockService.releaseLock(...); // add release lock here

await this.transactionPoolService.removeTransaction(...);

...

From our previous discussion during the txPool implementation, we know TransactionService.submitTransaction() does not throw and instead returns any error. It also doesn’t perform any critical async work. So it should be safe to call releaseLock immediately after it returns.

This approach avoids passing sessionKey and lockService through HapiService and SdkClient.

This'd make the code cleaner, scoped within TransactionService class, while keeping the behavior exactly the same.

Let me know what you think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i will move it, I was wondering if it would affect performance even with a little bit, currently the locks slow down significantly the process of sending a transaction but the difference is under 100ms so its fine. Will fix

await this.validateRawTransaction(parsedTx, networkGasPriceInWeiBars, requestDetails);
// Acquire lock FIRST - before any side effects or async operations
// This ensures proper nonce ordering for transactions from the same sender
if (parsedTx.from && ConfigService.get('ENABLE_NONCE_ORDERING')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as we did in the transaction pool implementation, would it make sense to abstract this feature-specific flag into its own service class?

In this case, ConfigService.get('ENABLE_NONCE_ORDERING') could be encapsulated within LockService, similar to how TransactionPoolService handles its flag.


// Create HAPI service
const hapiService = new HAPIService(this.logger, this.register, hbarLimitService);
const lockService = new LockService(LockStrategyFactory.create(this.redisClient, this.logger));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the comment above this line is misleading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

konstantinabl and others added 2 commits November 21, 2025 11:12
Co-authored-by: Logan Nguyen <[email protected]>
Signed-off-by: konstantinabl <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate LockService in ethSendRawTransaction

3 participants