Skip to content

feat: Add Temporary Key Caching System with Read-Only Client Support#241

Merged
cristiam86 merged 13 commits into
mainfrom
dxp-483-avoid-having-to-unlock-the-keystore-when-reading-a-contract
Jul 23, 2025
Merged

feat: Add Temporary Key Caching System with Read-Only Client Support#241
cristiam86 merged 13 commits into
mainfrom
dxp-483-avoid-having-to-unlock-the-keystore-when-reading-a-contract

Conversation

@epsjunior

@epsjunior epsjunior commented Jul 22, 2025

Copy link
Copy Markdown
Contributor

Add Temporary Key Caching System with Read-Only Client Support

Summary

Implements a temporary file caching system for decrypted private keys and introduces read-only client support to eliminate unnecessary password prompts. This enhancement addresses the inconvenience of requiring keystore passwords for read operations and provides automatic key caching for write operations, significantly improving developer experience and workflow efficiency.

Changes

🚀 New Features

  • Read-Only Client Support: NEW - Added readOnly parameter to getClient() to support contract reads without password prompts
  • Temporary Key Caching: Automatically caches decrypted private keys to avoid repeated password prompts for write operations
  • 5-Minute Auto-Expiration: Temporary files automatically expire after 5 minutes for security
  • Generic Temp File System: Extensible temporary file management for any content type
  • Address Validation: Cached keys are validated against current keystore address for security
  • Secure File Permissions: Temporary files created with owner-only access (0o600)

🎯 Problem Solved

Issue: The GenLayer CLI was asking for keystore password to unlock the wallet for reading contracts, which is inconvenient and should only be required when calling write operations.

Solution:

  • Read Operations: Now use address-only clients that don't require private key decryption
  • Write Operations: Use cached private keys to avoid repeated password prompts within 5-minute sessions

🔧 Infrastructure Updates

  • System Temp Directory: Uses os.tmpdir() for temporary file storage following OS conventions
  • Automatic Cleanup: Expired files are automatically cleaned on BaseAction initialization
  • Timestamp Tracking: Each cached file includes creation timestamp for expiration logic
  • Cross-Platform Support: Works seamlessly across Unix, macOS, and Windows systems

🏗️ Implementation Details

  • Smart Client Creation: Automatically chooses read-only vs full client based on operation type
  • Seamless Integration: Works with existing authentication flow without breaking changes
  • Security First: Address verification prevents using wrong cached keys
  • Smart Caching: Only caches keys after successful decryption

📁 File Structure

src/lib/
├── actions/
│   └── BaseAction.ts          # Read-only client support + temp key caching
├── config/
│   └── ConfigFileManager.ts   # Generic temp file management
└── interfaces/
    └── KeystoreData.ts        # Existing keystore interface

System Temp Directory:
/tmp/genlayer-temp/             # Unix/macOS
%TEMP%\genlayer-temp\          # Windows
├── decrypted_private_key      # Cached private key with timestamp
└── [future temp files]       # Extensible for other temporary data

🔐 Security Features

  • Read-Only Safety: Read operations never access or require private keys
  • Owner-Only Permissions: Temp files created with 0o600 (owner read/write only)
  • Address Validation: Cached keys verified against current keystore address
  • Auto-Cleanup: Invalid/expired keys automatically removed
  • Isolated Storage: Temp files in dedicated directory separate from config
  • No Persistent Storage: Keys only cached temporarily, never permanently stored

🧪 Testing

  • ConfigFileManager Tests (tests/libs/configFileManager.test.ts):

    • Temp file creation with timestamps and permissions
    • File retrieval with expiration checking
    • Automatic cleanup of expired files
    • Edge cases for missing files and directories
    • Path-specific mocking for comprehensive coverage
  • BaseAction Tests (tests/libs/baseAction.test.ts):

    • NEW - Read-only account access for address-only operations
    • Cached key usage and fallback scenarios
    • Integration with existing authentication flow
    • Comprehensive mocking for file system operations
  • Action Tests (Various action test files):

    • Updated all action tests to use new getAccount method
    • Proper mocking of genlayer-js createAccount function
    • Integration testing with cached key scenarios

🔧 Usage

Read Operations (NEW - No Password Required)

# Read contract data - NO PASSWORD PROMPT
genlayer call 0x123... viewFunction
# ✓ Data retrieved successfully (no authentication needed)

genlayer call 0x123... getBalance
# ✓ Balance retrieved (no password required)

Write Operations (First Time - Password Required)

# First write operation - prompts for password and caches key
genlayer deploy --contract contracts/MyContract.py
# Enter password: ********
# ✓ Contract deployed successfully

genlayer write 0x123... myMethod --args "value"
# ✓ Transaction sent successfully (no password prompt - uses cached key)

Session Management (Auto-Expiration)

# Within 5 minutes - uses cached key
genlayer write 0x123... anotherMethod
# ✓ Transaction sent (no password prompt)

# After 5+ minutes - requires password again
genlayer deploy --contract contracts/AnotherContract.py
# Enter password: ********  (prompts again due to expiration)
# ✓ Contract deployed successfully

✨ Code Quality

  • Operation-Aware Authentication: Distinguishes between read and write operations automatically
  • Generic Architecture: Temp file system supports any content type for future expansion
  • TypeScript Integration: Full type safety with proper interfaces
  • OS Conventions: Follows platform-specific temporary directory standards
  • Comprehensive Testing: 100% test coverage for all new functionality

🎯 Authentication Flow

  • Read Operations: getClient(rpcUrl, readOnly: true) → Address-only client → No password needed
  • Write Operations: getClient(rpcUrl, readOnly: false) → Full client → Password/cached key required
  • Fallback Logic: Cached key → Password prompt → Error handling

📊 Performance Benefits

  • Zero Authentication for Reads: Eliminates unnecessary password prompts for view operations
  • Reduced Write Authentication: Eliminates 3-5 second password decryption after first use
  • Improved Workflow: Seamless multi-command sessions without interruption
  • Faster Read Operations: Read-only mode bypasses private key operations entirely
  • Efficient Caching: Minimal disk space usage with automatic cleanup

🛡️ Security Considerations

  • Principle of Least Privilege: Read operations never access private keys
  • Time-Limited Write Access: 5-minute expiration balances convenience and security
  • Secure Permissions: Files accessible only to owner user account
  • No Network Exposure: All operations local to user's machine
  • Clean Exit: Temp files automatically cleaned on CLI restart

🔗 Dependencies

  • GenLayer-JS Integration: Enhanced to support both read-only and full clients
  • Built on Existing Infrastructure: Uses established ConfigFileManager and BaseAction patterns
  • OS Module Integration: Leverages Node.js os.tmpdir() for cross-platform support
  • File System Operations: Standard fs module operations with proper error handling
  • Ethers.js Compatibility: Works seamlessly with existing wallet decryption

🛡️ Backward Compatibility

  • No Breaking Changes: All existing commands work identically
  • Automatic Optimization: Read operations automatically benefit from no-password flow
  • Existing Config Preserved: No changes to permanent configuration files
  • Command Interface Unchanged: All CLI commands maintain same syntax

Testing: ✅ All tests pass with 100% coverage on new code
Security: ✅ Secure file permissions, address validation, and read-only safety
Performance: ✅ Zero authentication for reads, reduced authentication for writes
Cross-Platform: ✅ Works on Unix, macOS, and Windows
User Experience: ✅ Major workflow improvement eliminating unnecessary password prompts
Future-Proof: ✅ Generic temp file system for extensibility

Summary by CodeRabbit

  • New Features

    • Introduced secure temporary file storage for caching decrypted keys, reducing repeated password prompts and improving user experience.
    • Added support for read-only account access, allowing actions that do not require private key decryption.
  • Improvements

    • Enhanced keypair management with automatic cleanup of expired temporary files.
    • Improved password handling with configurable limits and clearer error messages.
  • Bug Fixes

    • Updated tests to align with new account retrieval and temporary file handling logic, ensuring more robust and accurate test coverage.

@coderabbitai

coderabbitai Bot commented Jul 22, 2025

Copy link
Copy Markdown

Walkthrough

This change introduces a read-only mode for account retrieval and client instantiation, refactors key management to cache decrypted keys in temporary files, and adds robust temporary file lifecycle management. It updates the getClient and related methods, enhances tests to reflect the new account retrieval logic, and adds comprehensive tests for temporary file handling.

Changes

File(s) Change Summary
src/commands/contracts/call.ts Modified call to getClient to include a readOnly boolean argument.
src/lib/actions/BaseAction.ts Refactored key/account management: added constants, temp file caching, read-only mode, and new methods.
src/lib/config/ConfigFileManager.ts Added temp file management: storing, retrieving, expiring, and cleaning up temporary files.
tests/actions/appeal.test.ts
tests/actions/call.test.ts
tests/actions/deploy.test.ts
tests/actions/receipt.test.ts
tests/actions/write.test.ts
Updated mocks to use getAccount instead of getPrivateKey in test setups.
tests/libs/baseAction.test.ts Enhanced tests for BaseAction: added mocks, new tests for caching/read-only, updated to use getAccount.
tests/libs/configFileManager.test.ts Added comprehensive tests for temp file operations and minor updates to existing config tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI_Command
    participant BaseAction
    participant ConfigFileManager
    participant GenLayerClient

    User->>CLI_Command: Initiate contract call (read-only)
    CLI_Command->>BaseAction: getClient(rpc, readOnly=true)
    BaseAction->>ConfigFileManager: getTempFile(TEMP_KEY_FILENAME)
    alt Temp file exists and valid
        ConfigFileManager-->>BaseAction: Return cached private key
    else
        BaseAction->>ConfigFileManager: Read keystore file
        alt readOnly
            BaseAction-->>CLI_Command: Return address only
        else
            BaseAction->>User: Prompt for password
            BaseAction->>ConfigFileManager: storeTempFile(TEMP_KEY_FILENAME, privateKey)
        end
    end
    BaseAction->>GenLayerClient: Instantiate client (readOnly)
    GenLayerClient-->>CLI_Command: Ready for use
Loading

Estimated code review effort

3 (~45 minutes)

Possibly related PRs

Suggested reviewers

  • cristiam86

Poem

A rabbit found a clever trick,
To keep your keys both safe and quick.
With temp files neat and passwords few,
Read-only calls now hop right through!
The cache is warm, the tests are bright—
Secure and swift, code’s future’s light!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-07-22T06_02_53_220Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 458a378 and 1bb470c.

📒 Files selected for processing (2)
  • src/lib/config/ConfigFileManager.ts (3 hunks)
  • tests/libs/configFileManager.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/libs/configFileManager.test.ts
  • src/lib/config/ConfigFileManager.ts
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5ef4bc and 458a378.

📒 Files selected for processing (10)
  • src/commands/contracts/call.ts (1 hunks)
  • src/lib/actions/BaseAction.ts (4 hunks)
  • src/lib/config/ConfigFileManager.ts (3 hunks)
  • tests/actions/appeal.test.ts (1 hunks)
  • tests/actions/call.test.ts (1 hunks)
  • tests/actions/deploy.test.ts (1 hunks)
  • tests/actions/receipt.test.ts (1 hunks)
  • tests/actions/write.test.ts (1 hunks)
  • tests/libs/baseAction.test.ts (5 hunks)
  • tests/libs/configFileManager.test.ts (2 hunks)
🧬 Code Graph Analysis (2)
tests/libs/configFileManager.test.ts (1)
src/lib/config/ConfigFileManager.ts (1)
  • ConfigFileManager (10-124)
src/lib/actions/BaseAction.ts (2)
src/lib/config/ConfigFileManager.ts (1)
  • ConfigFileManager (10-124)
src/lib/interfaces/KeystoreData.ts (1)
  • KeystoreData (1-5)
🧰 Additional context used
🧬 Code Graph Analysis (2)
tests/libs/configFileManager.test.ts (1)
src/lib/config/ConfigFileManager.ts (1)
  • ConfigFileManager (10-124)
src/lib/actions/BaseAction.ts (2)
src/lib/config/ConfigFileManager.ts (1)
  • ConfigFileManager (10-124)
src/lib/interfaces/KeystoreData.ts (1)
  • KeystoreData (1-5)
🔇 Additional comments (28)
src/lib/config/ConfigFileManager.ts (8)

5-8: LGTM: Clean interface design for temporary file data.

The TempFileData interface appropriately encapsulates the content and timestamp needed for expiration tracking.


13-14: LGTM: Well-defined constants for temporary file management.

The temp folder path using OS temp directory and 5-minute expiration time are appropriate choices for balancing security and usability.


19-22: LGTM: Proper initialization of temporary folder system.

The constructor correctly initializes the temp folder path and ensures folder existence. Good integration with existing initialization flow.


37-41: LGTM: Secure temporary folder creation with owner-only permissions.

The method correctly creates the temp folder with restrictive permissions (0o700) to prevent unauthorized access. Good security practice.


67-75: LGTM: Secure temporary file storage implementation.

The method properly:

  • Ensures temp folder exists
  • Creates timestamped data structure
  • Uses secure file permissions (0o600) for owner-only access
  • Handles JSON serialization correctly

77-93: LGTM: Robust temporary file retrieval with expiration handling.

The method correctly:

  • Handles non-existent files gracefully
  • Parses JSON data safely
  • Implements expiration logic with automatic cleanup
  • Returns null for expired files

95-97: LGTM: Simple and efficient existence check.

The method leverages the existing getTempFile method, which already handles expiration checking.


99-104: LGTM: Safe temporary file cleanup.

The method properly checks file existence before attempting deletion to avoid errors.

tests/actions/write.test.ts (1)

22-22: LGTM: Test correctly updated to reflect BaseAction API refactor.

The change from mocking getPrivateKey to getAccount aligns with the refactor in BaseAction where account retrieval now returns an object containing the private key rather than the key directly.

tests/actions/appeal.test.ts (1)

24-24: LGTM: Consistent test update for BaseAction API refactor.

The mock update properly reflects the change from getPrivateKey to getAccount method, maintaining test functionality while adapting to the new API.

tests/actions/receipt.test.ts (1)

26-26: LGTM: Test properly aligned with BaseAction API changes.

The mock update maintains consistency with the refactored BaseAction class interface while preserving test functionality.

tests/actions/deploy.test.ts (1)

29-29: LGTM: Final test file consistently updated for API refactor.

The mock change completes the consistent update across all action test files to work with the new getAccount method signature.

src/commands/contracts/call.ts (1)

24-24: LGTM: Correctly implements read-only client for contract calls.

This change appropriately sets the readOnly parameter to true for contract call operations, which eliminates unnecessary password prompts since read operations don't require private key access.

tests/actions/call.test.ts (1)

24-24: LGTM: Test correctly updated to reflect BaseAction refactor.

The change from mocking getPrivateKey to getAccount with an object return value properly aligns with the BaseAction refactor where getPrivateKey was replaced by getAccount returning an account object.

tests/libs/baseAction.test.ts (5)

9-22: LGTM: Comprehensive mock setup for new dependencies.

The addition of mocks for fs, os, and genlayer-js properly supports testing the new temporary file handling and OS path functionality introduced in the BaseAction refactor.


60-90: LGTM: Well-structured temp file method mocking.

The mock setup for temporary file methods (storeTempFile, getTempFile, clearTempFile, cleanupExpiredTempFiles) provides comprehensive coverage for testing the new caching functionality.


252-268: LGTM: Test correctly updated for getAccount method.

The test properly verifies the new getAccount method returns an account object with a private key, and validates the expected file system interactions.


262-268: LGTM: Excellent test coverage for read-only functionality.

This test correctly verifies that when getAccount is called with readOnly=true, it returns only the address without requiring private key decryption, which is the core functionality for eliminating password prompts on read operations.


293-301: LGTM: Critical test for caching functionality.

This test verifies that cached keys are properly retrieved and reused, avoiding repeated password prompts. The test correctly mocks getTempFile and verifies that inquirer.prompt is not called when a cached key exists.

tests/libs/configFileManager.test.ts (1)

113-290: LGTM: Comprehensive test coverage for temporary file functionality.

The new test suite thoroughly covers all aspects of the temporary file system:

  • File creation with proper timestamps and permissions (mode 0o600)
  • Temp folder creation with secure permissions (mode 0o700)
  • Expiration logic with 5-minute timeout
  • Retrieval of valid and expired files
  • Cleanup of expired files while preserving valid ones
  • Edge cases for non-existent files and folders

The tests properly mock Date.now() to control timestamp behavior and verify the security model with owner-only file permissions.

src/lib/actions/BaseAction.ts (8)

14-17: LGTM: Well-defined constants improve maintainability.

The introduction of constants for default paths, limits, and filenames eliminates magic numbers and makes the codebase more maintainable and configurable.


25-25: LGTM: Proactive cleanup on initialization.

Calling cleanupExpiredTempFiles() in the constructor ensures stale temporary files are removed when the CLI starts, maintaining good hygiene for the temporary storage system.


36-37: LGTM: Secure temporary key caching implementation.

The caching of decrypted private keys after successful decryption is well-implemented. The temporary file system uses secure permissions (0o600) and 5-minute expiration as defined in ConfigFileManager, balancing security with user convenience.


64-76: LGTM: Clean read-only client implementation.

The getClient method properly handles the new readOnly parameter and delegates account retrieval to the new getAccount method. The logic maintains backward compatibility with the default readOnly=false parameter.


78-108: LGTM: Robust account retrieval with caching and read-only support.

The getAccount method excellently implements the core functionality:

  • Read-only optimization: Returns only the address for read operations (lines 99-101)
  • Smart caching: Checks for cached decrypted keys before prompting for passwords (lines 104-106)
  • Fallback handling: Creates new keypairs when files are missing or invalid
  • Address validation: Ensures cached keys match the current keystore address through the validation in ConfigFileManager

The method properly balances security (temporary file expiration, secure permissions) with user experience (eliminating redundant password prompts).


110-112: LGTM: Simple and effective address extraction.

The getAddress helper method provides a clean way to extract the address from keystore data for read-only operations, with proper type casting to the Address type.


149-149: LGTM: Proper cache invalidation on keypair creation.

Clearing the temporary decrypted key file when creating a new keypair ensures that stale cached keys don't persist after keypair changes, maintaining security and consistency.


133-135: LGTM: Consistent use of constants for validation.

Using the MIN_PASSWORD_LENGTH constant in both the validation logic and error message ensures consistency and makes the password policy easily configurable.

Comment thread src/lib/config/ConfigFileManager.ts
Comment thread src/lib/config/ConfigFileManager.ts

@cristiam86 cristiam86 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@epsjunior minor comment about the permissions in the temp file

@cristiam86 cristiam86 merged commit 89d57f9 into main Jul 23, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants