Skip to content

fix: use Option<CryptoHash> for Transaction block_hash#135

Open
r-near wants to merge 2 commits intomainfrom
fix/signed-transaction-block-hash
Open

fix: use Option<CryptoHash> for Transaction block_hash#135
r-near wants to merge 2 commits intomainfrom
fix/signed-transaction-block-hash

Conversation

@r-near
Copy link
Copy Markdown
Contributor

@r-near r-near commented Mar 5, 2026

Summary

Fixes #134 — the TryFrom<SignedTransactionView> conversion was placing the transaction hash into the block_hash field, since the RPC response doesn't include the block hash used at signing time. This caused SignedTransaction::get_hash() to return incorrect values.

  • Changes block_hash from CryptoHash to Option<CryptoHash> on TransactionV0 and TransactionV1, forcing consumers to handle the missing case
  • Uses custom borsh serialize_with/deserialize_with to preserve the on-chain wire format (panics with a clear message if you try to serialize a None block hash)
  • Pre-populates the SignedTransaction hash cache with the correct tx hash from the RPC response
  • Adds Transaction::block_hash() accessor returning Option<CryptoHash>

Breaking change: block_hash field type changed from CryptoHash to Option<CryptoHash> on TransactionV0/TransactionV1.

Test plan

  • cargo check passes
  • cargo test --lib passes (all 40 tests)
  • Verify downstream consumers handle the new Option type

The RPC `SignedTransactionView` response contains the transaction hash
but not the block hash that was used when signing. Previously, the
`TryFrom<SignedTransactionView>` conversion incorrectly placed the
transaction hash into the `block_hash` field, causing `get_hash()` to
return wrong values.

This changes `block_hash` to `Option<CryptoHash>` on `TransactionV0`
and `TransactionV1`, so consumers are forced to handle the missing case.
Custom borsh serialize/deserialize helpers preserve the on-chain wire
format. The `SignedTransaction` hash is now pre-populated from the RPC
response so `get_hash()` returns the correct value.

Closes #134
Copilot AI review requested due to automatic review settings March 5, 2026 16:59
@r-near r-near requested review from a team and akorchyn as code owners March 5, 2026 16:59
@github-project-automation github-project-automation bot moved this to NEW❗ in DevTools Mar 5, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 51.42857% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.90%. Comparing base (37fee2c) to head (26336cf).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
types/src/transaction/mod.rs 51.42% 17 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #135   +/-   ##
=======================================
  Coverage   50.90%   50.90%           
=======================================
  Files          40       40           
  Lines        5265     5298   +33     
=======================================
+ Hits         2680     2697   +17     
- Misses       2585     2601   +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

let signed = Self::new(Signature::from_str(&signature)?, transaction);
// Pre-populate with the correct hash from the RPC response,
// since we cannot recompute it without the block hash.
let _ = signed.hash.set(tx_hash);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

signed.hash.set(tx_hash) returns a Result indicating whether the OnceLock was initialized. Ignoring it can mask unexpected state (e.g., if SignedTransaction::new ever starts pre-populating the cache) and could reintroduce incorrect hashes silently. Consider handling the Result explicitly (e.g., expect with a message or propagate/return an error) so failures are visible.

Suggested change
let _ = signed.hash.set(tx_hash);
#[allow(clippy::expect_used)]
signed
.hash
.set(tx_hash)
.expect("SignedTransaction hash OnceLock unexpectedly initialized");

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +31
fn borsh_ser_optional_hash<W: std::io::Write>(
val: &Option<CryptoHash>,
writer: &mut W,
) -> Result<(), std::io::Error> {
let hash = val.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"cannot borsh-serialize a Transaction whose block_hash is None \
(this transaction was deserialized from an RPC response that \
lacks block hash information)",
)
})?;
BorshSerialize::serialize(&hash, writer)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

borsh_ser_optional_hash currently calls ok_or_else directly on &Option<CryptoHash>, which works because Option<CryptoHash> is Copy today, but is a bit non-obvious and introduces an unnecessary copy. Using as_ref()/as_deref() (or a match) to get a borrowed hash before serializing would make the intent clearer and avoid relying on Copy semantics.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: NEW❗

Development

Successfully merging this pull request may close these issues.

Invalid conversion of signed transaction

3 participants