Skip to content

fix: RPC should return error on failed contract deployment instead of address#1276

Open
kstroobants wants to merge 1 commit intomainfrom
dxp-186-rpc-should-return-error-on-failed-contract-deployment
Open

fix: RPC should return error on failed contract deployment instead of address#1276
kstroobants wants to merge 1 commit intomainfrom
dxp-186-rpc-should-return-error-on-failed-contract-deployment

Conversation

@kstroobants
Copy link
Contributor

@kstroobants kstroobants commented Jul 29, 2025

Fixes #DXP-186

What

  • Added a hide_fields parameter to the get_transaction_by_hash method in TransactionsProcessor to conditionally hide certain fields in the transaction data. Internally we do not want to hide those field because we use them, but externally like for an endpoint call we want to hide them.
  • Updated the get_transaction_by_hash function in endpoints.py to pass True for the hide_fields parameter.
  • Modified the logic in get_transaction_receipt to determine the contractAddress based on: only show the contract address when it is a deployment transaction and when the contract was deployed.

Why

  • To be more user friendly to not show the contract address when the contract is not deployed.
  • To ensure the correct contract address is returned in transaction receipts.

Testing done

  • Verified that the get_transaction_by_hash method correctly hides fields and the consensus is still working.
  • Tested the get_transaction_receipt function to ensure it returns the correct contractAddress based on the new logic.

Decisions made

Checks

  • I have tested this code
  • I have reviewed my own PR
  • I have created an issue for this PR
  • I have set a descriptive PR title compliant with conventional commits

Reviewing tips

  • Pay attention to the new hide_fields parameter and its impact on transaction data.
  • Review the logic changes in get_transaction_receipt for determining the contractAddress.

User facing release notes

  • Hide the contract address when the contract is not deployed.
  • Refined logic for the transaction receipt's contract address.

Summary by CodeRabbit

  • New Features

    • Optional concealment of sensitive fields in transaction details for contract deployments when specific success conditions are not met.
  • Bug Fixes

    • Transaction receipts now report contract addresses only when deployment and consensus conditions indicate a successful deployment.

✏️ Tip: You can customize this high-level summary in your review settings.

@kstroobants kstroobants self-assigned this Jul 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 29, 2025

📝 Walkthrough

Walkthrough

Added an optional hide_fields parameter to get_transaction_by_hash to allow conditional redaction of contract-related fields for DEPLOY_CONTRACT transactions; RPC endpoints were updated to call this with hide_fields=True and receipt logic now computes contractAddress based on consensus and leader_receipt conditions.

Changes

Cohort / File(s) Change Summary
Transaction Processor Logic
backend/database_handler/transactions_processor.py
Added hide_fields: bool = False to get_transaction_by_hash. When hide_fields is true and the transaction is DEPLOY_CONTRACT, contract-related fields (contract_address, contract_snapshot) are redacted unless last_round.result == 6, consensus_data.leader_receipt exists/non-empty, and the first leader_receipt.execution_result == SUCCESS.
RPC Endpoint Adjustments
backend/protocol_rpc/endpoints.py
Calls to get_transaction_by_hash updated to pass hide_fields=True. get_transaction_receipt now derives contractAddress via the same multi-condition check (transaction type, last_round.result, leader_receipt existence, execution_result) and sets contractAddress accordingly; to_address extraction made conditional to yield None when falsy.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Endpoint
    participant TransactionsProcessor
    Note over Endpoint: get_transaction_by_hash / get_block_by_hash
    Client->>Endpoint: Request transaction by hash
    Endpoint->>TransactionsProcessor: get_transaction_by_hash(hash, hide_fields=True)
    TransactionsProcessor->>TransactionsProcessor: _process_round_data -> evaluate hide_fields & type
    alt Conditions for showing contract met
        TransactionsProcessor-->>Endpoint: Return transaction with contract fields
    else
        TransactionsProcessor-->>Endpoint: Return transaction with contract fields redacted/removed
    end
    Endpoint-->>Client: Respond with transaction data
Loading
sequenceDiagram
    participant Client
    participant Endpoint
    participant TransactionsProcessor
    Note over Endpoint: get_transaction_receipt flow
    Client->>Endpoint: Request transaction receipt
    Endpoint->>TransactionsProcessor: get_transaction_by_hash(hash, hide_fields=True)
    TransactionsProcessor-->>Endpoint: Return transaction
    Endpoint->>Endpoint: Check type == DEPLOY_CONTRACT, last_round.result==6, leader_receipt exists, execution_result==SUCCESS
    alt All conditions met
        Endpoint-->>Client: Respond with contractAddress = transaction["to_address"]
    else
        Endpoint-->>Client: Respond with contractAddress = None
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • cristiam86

Poem

A rabbit peeked behind the code, so neat,
Hiding contract trails with nimble feet.
By consensus light and receipts so bright,
It shows the address only when conditions are right.
Hop on, tiny guard, keep secrets tight. 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preventing the RPC from returning a contract address on failed deployments, which matches the core objective of the PR.
Description check ✅ Passed The description covers all required template sections with appropriate detail: What (parameter addition and endpoint updates), Why (user-friendliness and correctness), Testing done (field hiding and contractAddress logic), and Checks (all marked complete).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@sherlock-ai-github-agent
Copy link

PR Summary

Here's a concise summary of the Pull Request changes based on the provided reviews:

  • No Functional Code Changes:

    • Multiple reviews confirm there are no actual code modifications across all contracts (Messages.sol, Transactions.sol, ConsensusMain.sol, MockGenStaking.sol).
    • Identical implementations for all functions, state variables, modifiers, and error handling.
    • No structural or cosmetic changes visible in the diffs.
  • Possible Explanations:
    • Documentation-only update (though comments appear unchanged)
    • Mistaken PR submission without intended changes
    • Formatting/whitespace adjustments not visible in the diff

  • Recommendations:

    1. Verify if this was an intentional no-change PR
    2. Confirm intended changes weren't omitted during submission
    3. Check for changes in files not shown in the diff

Conclusion: This appears to be an empty PR with identical pre/post code. Developers should clarify its purpose before merging.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
5.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@sherlock-ai-github-agent
Copy link

Sherlock AI Findings

The automated tool identified the following potential security issues in the codebase. Please review the details for each issue in the linked dashboard.

# Title Severity Details
1 Missing Check for Already Processed Transactions Allowing Replay Attacks Medium View Details

Next Steps: Review the linked issues in the dashboard and address high-severity bugs first. Contact the team if you need assistance.

Full report available at: https://ai.sherlock.xyz/runs/6251b583-ea18-4eca-a78b-d11a7eec2560

@kstroobants kstroobants requested a review from epsjunior July 30, 2025 01:46
@danieljrc888 danieljrc888 force-pushed the dxp-186-rpc-should-return-error-on-failed-contract-deployment branch from 1835cb4 to 821a454 Compare January 8, 2026 17:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/protocol_rpc/endpoints.py (1)

1424-1431: Missing None check for transaction causes crash if not found.

get_transaction_by_hash can return None. If the transaction doesn't exist, line 1430 will raise AttributeError: 'NoneType' object has no attribute 'get'.

🐛 Suggested fix
     transaction = transactions_processor.get_transaction_by_hash(transaction_hash)
 
+    if transaction is None:
+        raise NotFoundError(
+            message=f"Transaction {transaction_hash} not found",
+            data={"hash": transaction_hash},
+        )
+
     event_signature = "NewTransaction(bytes32,address,address)"
     event_signature_hash = eth_utils.keccak(text=event_signature).hex()
 
     to_addr = transaction.get("to_address") if transaction.get("to_address") else None
🤖 Fix all issues with AI agents
In @backend/database_handler/transactions_processor.py:
- Line 590: The method signature line with parameters (self, transaction_hash:
str, sim_config: dict | None = None, hide_fields: bool = False) exceeds Black's
line length and must be reformatted; edit the method signature (the
function/method definition containing transaction_hash, sim_config and
hide_fields) so the parameters are broken across multiple lines (one parameter
per line or wrapped to satisfy Black), include a trailing comma if needed and
preserve the existing type hints and default values so Black will accept the
file.
- Around line 634-649: Guard against None consensus_data and replace the magic
number: in the block inside transactions_processor where you check
transaction_data["type"] == TransactionType.DEPLOY_CONTRACT.value and inspect
transaction_data["last_round"]["result"] == 6 and "leader_receipt" in
transaction_data["consensus_data"], first ensure consensus =
transaction_data.get("consensus_data") is not None and is a mapping (e.g., if
consensus and "leader_receipt" in consensus) before using the membership test to
avoid TypeError; also replace the literal 6 with the named constant (e.g.,
ConsensusResult.MAJORITY_AGREE.value) when comparing
transaction_data["last_round"]["result"] to improve clarity and maintainability,
keeping the rest of the existing checks on leader_receipt and execution_result
intact.

In @backend/protocol_rpc/endpoints.py:
- Line 1497: get_transaction_by_hash is being called with True as a positional
second argument, which is being bound to sim_config (expects dict|None); change
the call to pass sim_config explicitly (e.g., None) and pass hide_fields as a
keyword (hide_fields=True) or use hide_fields=True and sim_config=None by name
so the boolean goes to hide_fields; update the call at
transactions_processor.get_transaction_by_hash(block_hash, ...) accordingly.
🧹 Nitpick comments (1)
backend/protocol_rpc/endpoints.py (1)

1430-1430: Consider simplifying the falsy check.

The current pattern transaction.get("to_address") if transaction.get("to_address") else None calls get() twice. A cleaner approach:

♻️ Optional simplification
-    to_addr = transaction.get("to_address") if transaction.get("to_address") else None
+    to_addr = transaction.get("to_address") or None
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1835cb4 and 821a454.

📒 Files selected for processing (2)
  • backend/database_handler/transactions_processor.py
  • backend/protocol_rpc/endpoints.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Target Python 3.12, use 4-space indentation, and rely on Black via pre-commit for formatting consistency

**/*.py: Apply Black formatter for Python code formatting
Include type hints in all Python code

Files:

  • backend/database_handler/transactions_processor.py
  • backend/protocol_rpc/endpoints.py
backend/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Align backend filenames with their behavior (e.g., validators/llm_validator.py) and mirror that pattern in tests

Files:

  • backend/database_handler/transactions_processor.py
  • backend/protocol_rpc/endpoints.py
🧬 Code graph analysis (2)
backend/database_handler/transactions_processor.py (2)
backend/domain/types.py (1)
  • TransactionType (147-151)
backend/node/types.py (1)
  • ExecutionResultStatus (123-132)
backend/protocol_rpc/endpoints.py (3)
backend/domain/types.py (1)
  • TransactionType (147-151)
backend/node/types.py (1)
  • ExecutionResultStatus (123-132)
backend/database_handler/transactions_processor.py (1)
  • get_transaction_by_hash (589-650)
🪛 GitHub Actions: pre-commit
backend/database_handler/transactions_processor.py

[error] 1-1: Black formatting failed. File reformatted by Black hook; commit changes or run 'pre-commit run --all-files' to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: backend-unit-tests
🔇 Additional comments (1)
backend/protocol_rpc/endpoints.py (1)

1050-1051: LGTM!

The call correctly passes sim_config as the second argument and True for hide_fields as the third argument.


def get_transaction_by_hash(
self, transaction_hash: str, sim_config: dict | None = None
self, transaction_hash: str, sim_config: dict | None = None, hide_fields: bool = False
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Black formatting to pass CI.

The pipeline failure indicates Black reformatted this line. The parameter list on a single line likely exceeds the line length limit.

🔧 Suggested fix
     def get_transaction_by_hash(
-        self, transaction_hash: str, sim_config: dict | None = None, hide_fields: bool = False
+        self,
+        transaction_hash: str,
+        sim_config: dict | None = None,
+        hide_fields: bool = False,
     ) -> dict | None:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self, transaction_hash: str, sim_config: dict | None = None, hide_fields: bool = False
self,
transaction_hash: str,
sim_config: dict | None = None,
hide_fields: bool = False,
🤖 Prompt for AI Agents
In @backend/database_handler/transactions_processor.py at line 590, The method
signature line with parameters (self, transaction_hash: str, sim_config: dict |
None = None, hide_fields: bool = False) exceeds Black's line length and must be
reformatted; edit the method signature (the function/method definition
containing transaction_hash, sim_config and hide_fields) so the parameters are
broken across multiple lines (one parameter per line or wrapped to satisfy
Black), include a trailing comma if needed and preserve the existing type hints
and default values so Black will accept the file.

Comment on lines +634 to +649
if hide_fields:
if transaction_data[
"type"
] == TransactionType.DEPLOY_CONTRACT.value and not (
transaction_data["last_round"]["result"] == 6
and "leader_receipt" in transaction_data["consensus_data"]
and transaction_data["consensus_data"]["leader_receipt"] is not None
and len(transaction_data["consensus_data"]["leader_receipt"]) > 0
and transaction_data["consensus_data"]["leader_receipt"][0][
"execution_result"
]
== ExecutionResultStatus.SUCCESS.value
):
transaction_data["data"]["contract_address"] = None
if "contract_snapshot" in transaction_data:
del transaction_data["contract_snapshot"]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential TypeError if consensus_data is None; also use a named constant for magic number 6.

  1. If transaction_data["consensus_data"] is None and last_round["result"] == 6, the expression "leader_receipt" in transaction_data["consensus_data"] will raise TypeError: argument of type 'NoneType' is not iterable.

  2. The magic number 6 should be replaced with a named constant (e.g., ConsensusResult.MAJORITY_AGREE) for clarity and maintainability.

🔧 Suggested fix
+        from backend.consensus.types import ConsensusResult
+
         if hide_fields:
             if transaction_data[
                 "type"
             ] == TransactionType.DEPLOY_CONTRACT.value and not (
-                transaction_data["last_round"]["result"] == 6
-                and "leader_receipt" in transaction_data["consensus_data"]
+                transaction_data["last_round"]["result"]
+                == int(ConsensusResult.MAJORITY_AGREE)
+                and transaction_data["consensus_data"] is not None
+                and "leader_receipt" in transaction_data["consensus_data"]
                 and transaction_data["consensus_data"]["leader_receipt"] is not None
                 and len(transaction_data["consensus_data"]["leader_receipt"]) > 0
                 and transaction_data["consensus_data"]["leader_receipt"][0][
                     "execution_result"
                 ]
                 == ExecutionResultStatus.SUCCESS.value
             ):
                 transaction_data["data"]["contract_address"] = None
                 if "contract_snapshot" in transaction_data:
                     del transaction_data["contract_snapshot"]
🤖 Prompt for AI Agents
In @backend/database_handler/transactions_processor.py around lines 634 - 649,
Guard against None consensus_data and replace the magic number: in the block
inside transactions_processor where you check transaction_data["type"] ==
TransactionType.DEPLOY_CONTRACT.value and inspect
transaction_data["last_round"]["result"] == 6 and "leader_receipt" in
transaction_data["consensus_data"], first ensure consensus =
transaction_data.get("consensus_data") is not None and is a mapping (e.g., if
consensus and "leader_receipt" in consensus) before using the membership test to
avoid TypeError; also replace the literal 6 with the named constant (e.g.,
ConsensusResult.MAJORITY_AGREE.value) when comparing
transaction_data["last_round"]["result"] to improve clarity and maintainability,
keeping the rest of the existing checks on leader_receipt and execution_result
intact.

Comment on lines +1460 to +1471
if (
transaction["type"] == TransactionType.DEPLOY_CONTRACT.value
and transaction["last_round"]["result"] == 6
and "leader_receipt" in transaction["consensus_data"]
and transaction["consensus_data"]["leader_receipt"] is not None
and len(transaction["consensus_data"]["leader_receipt"]) > 0
and transaction["consensus_data"]["leader_receipt"][0]["execution_result"]
== ExecutionResultStatus.SUCCESS.value
):
contract_address = transaction["to_address"]
else:
contract_address = None
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Duplicated logic and magic number 6; also potential TypeError if consensus_data is None.

  1. This condition duplicates the logic in transactions_processor.py (lines 634-646). Consider extracting to a shared helper function for maintainability.

  2. The magic number 6 should use a named constant (e.g., ConsensusResult.MAJORITY_AGREE).

  3. If transaction["consensus_data"] is None, the "leader_receipt" in transaction["consensus_data"] check will raise TypeError.

♻️ Suggested refactor
+from backend.consensus.types import ConsensusResult
+
+def _is_successful_deployment(transaction: dict) -> bool:
+    """Check if a DEPLOY_CONTRACT transaction was successfully deployed."""
+    return (
+        transaction["type"] == TransactionType.DEPLOY_CONTRACT.value
+        and transaction["last_round"]["result"] == int(ConsensusResult.MAJORITY_AGREE)
+        and transaction.get("consensus_data") is not None
+        and "leader_receipt" in transaction["consensus_data"]
+        and transaction["consensus_data"]["leader_receipt"] is not None
+        and len(transaction["consensus_data"]["leader_receipt"]) > 0
+        and transaction["consensus_data"]["leader_receipt"][0].get("execution_result")
+        == ExecutionResultStatus.SUCCESS.value
+    )
+
 # In get_transaction_receipt:
-    if (
-        transaction["type"] == TransactionType.DEPLOY_CONTRACT.value
-        and transaction["last_round"]["result"] == 6
-        and "leader_receipt" in transaction["consensus_data"]
-        and transaction["consensus_data"]["leader_receipt"] is not None
-        and len(transaction["consensus_data"]["leader_receipt"]) > 0
-        and transaction["consensus_data"]["leader_receipt"][0]["execution_result"]
-        == ExecutionResultStatus.SUCCESS.value
-    ):
-        contract_address = transaction["to_address"]
-    else:
-        contract_address = None
+    contract_address = transaction["to_address"] if _is_successful_deployment(transaction) else None

Committable suggestion skipped: line range outside the PR's diff.

) -> dict | None:

transaction = transactions_processor.get_transaction_by_hash(block_hash)
transaction = transactions_processor.get_transaction_by_hash(block_hash, True)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Type error: True is passed as sim_config instead of hide_fields.

The method signature is get_transaction_by_hash(transaction_hash, sim_config, hide_fields). Passing True positionally makes it the sim_config argument (which expects dict | None), not hide_fields.

🐛 Suggested fix
-    transaction = transactions_processor.get_transaction_by_hash(block_hash, True)
+    transaction = transactions_processor.get_transaction_by_hash(block_hash, None, True)

Or use keyword argument for clarity:

-    transaction = transactions_processor.get_transaction_by_hash(block_hash, True)
+    transaction = transactions_processor.get_transaction_by_hash(block_hash, hide_fields=True)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
transaction = transactions_processor.get_transaction_by_hash(block_hash, True)
transaction = transactions_processor.get_transaction_by_hash(block_hash, hide_fields=True)
🤖 Prompt for AI Agents
In @backend/protocol_rpc/endpoints.py at line 1497, get_transaction_by_hash is
being called with True as a positional second argument, which is being bound to
sim_config (expects dict|None); change the call to pass sim_config explicitly
(e.g., None) and pass hide_fields as a keyword (hide_fields=True) or use
hide_fields=True and sim_config=None by name so the boolean goes to hide_fields;
update the call at transactions_processor.get_transaction_by_hash(block_hash,
...) accordingly.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 8, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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.

1 participant