Skip to content

UtxoRpc server upgraded to 0.18.1 spec#813

Merged
scarmuega merged 11 commits intotxpipe:mainfrom
nicolasLuduena:feat/bump-u5c-0.18.1
Jan 16, 2026
Merged

UtxoRpc server upgraded to 0.18.1 spec#813
scarmuega merged 11 commits intotxpipe:mainfrom
nicolasLuduena:feat/bump-u5c-0.18.1

Conversation

@nicolasLuduena
Copy link
Copy Markdown
Member

@nicolasLuduena nicolasLuduena commented Dec 5, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Submission/evaluation now accept and return a single transaction/report per request.
    • Standardized invalid-argument errors for missing/unsupported transaction types.
  • Enhancements

    • Block references now include timestamps and improved lookup order (hash → height → slot).
    • Watch/stream emits an idle notification when new blocks contain no transactions.
    • UTXO payloads include an additional populated field; transaction fee in evaluation is now optional.
  • New Features

    • Historical UTXO lookup added to ledger adapter.
  • Chores

    • Updated workspace dependency revision for a cryptographic library.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 5, 2025

📝 Walkthrough

Walkthrough

Updates gRPC handlers to process a single transaction per request, add timestamp population to BlockRef constructions across sync/watch flows and a BlockRef fallback for Apply events with no transactions, add block_ref: None to UTXO data initialization, add historical UTXO lookup in the domain adapter, and bump the pallas workspace dependency rev.

Changes

Cohort / File(s) Summary
UTXO data initialization
src/serve/grpc/query.rs
Add block_ref: None to AnyUtxoData in UTXO conversion; no control-flow changes.
Single-transaction Submit/Eval
src/serve/grpc/submit.rs
Require and handle a single tx (Raw) per request for SubmitTx/EvalTx; return a single hash/report instead of lists; TxEval.fee becomes None; standardized invalid_argument errors for missing/unsupported tx types; removed per-tx loops.
Timestamped BlockRef / Sync flows
src/serve/grpc/sync.rs
map_block_cbor yields a block with timestamp; point_to_blockref signature now accepts a timestamp and populates BlockRef.timestamp; ReadTip/TipEvent/DumpHistory use computed slot timestamps; FetchBlock lookup extended to try hash → height → slot.
Watch flow fallback for Apply events
src/serve/grpc/watch.rs
Add raw_to_blockref helper to derive BlockRef from CBOR; roll_to_watch_response emits an Idle action with block ref when Apply maps to no txs, otherwise preserves Apply/Undo behavior.
Domain adapter: historical UTXOs
src/adapters.rs
Add get_historical_utxos(&self, refs: &[pallas::interop::utxorpc::TxoRef]) -> Option<...> implementation scanning WAL logs in reverse to resolve requested TxoRefs; returns Some(map) or None.
Workspace dependency bump
Cargo.toml
Update workspace pallas dependency git revision to ab27006 (features unchanged); previous rev/version lines adjusted/commented.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant Mapper
  participant Domain
  rect rgba(240,248,255,0.5)
  Client->>Server: SubmitTx(tx: Raw)
  Server->>Server: validate tx present & type
  Server->>Mapper: map tx -> internal Tx
  Server->>Domain: submit/persist tx
  Server-->>Client: SubmitTxResponse(hash)
  end

  rect rgba(250,240,245,0.5)
  Client->>Server: EvalTx(tx: Raw)
  Server->>Mapper: map tx -> TxEval input
  Server->>Domain: evaluate tx
  Server-->>Client: EvalTxResponse(report: AnyChainEval)
  end
Loading
sequenceDiagram
  participant ChainSource
  participant Mapper
  participant Server
  participant Client
  rect rgba(240,255,240,0.5)
  ChainSource->>Mapper: provide CBOR block (map_block_cbor)
  Mapper->>Mapper: extract header, timestamp, txs
  alt mapped block has txs
    Mapper->>Server: Apply with serialized block & txs
    Server-->>Client: FollowTipResponse { Apply + tip(with timestamp) }
  else no txs
    Mapper->>Server: Apply with serialized block (no txs)
    Server-->>Client: FollowTipResponse { Idle + block_ref(with timestamp) }
  end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • scarmuega

Poem

🐰 A single hop, one tx in paw,

Timestamps tucked where block headers saw,
If Apply is empty, Idle I sing,
Historical UTXOs — a backward spring,
Hopping onward with tidy ears and spring ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main objective of the changeset: upgrading the UtxoRpc server to the 0.18.1 specification, which is reflected across multiple files including dependency updates and API changes.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/adapters.rs (1)

124-155: Avoid full WAL scans when refs contains duplicates.

Early-exit currently compares against refs.len(), which can be larger than the number of unique refs. This can force a full WAL scan even when all unique refs are already found. Consider using the set size as the target.

♻️ Suggested change
         let refs_set: std::collections::HashSet<_> =
             refs.iter().copied().map(TxoRef::from).collect();
+        let target_len = refs_set.len();
@@
-            if result.len() == refs.len() {
+            if result.len() == target_len {
                 break;
             }

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29aced8 and 2624474.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • Cargo.toml
  • src/adapters.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/adapters.rs (1)
crates/core/src/lib.rs (8)
  • from (95-97)
  • from (101-103)
  • from (107-109)
  • from (140-142)
  • from (146-150)
  • from (157-159)
  • from (163-165)
  • era (79-81)
⏰ 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: Check Build
🔇 Additional comments (1)
Cargo.toml (1)

139-141: Lockfile is properly updated with rev ab27006; verify the UTxoRpc spec version alignment.

The Cargo.lock is correctly updated with rev ab27006 across all pallas dependencies. However, confirm that pallas-utxorpc at this commit supports UTxoRpc 0.18.1 — the lockfile shows pallas-utxorpc 1.0.0-alpha.3 but does not directly indicate which UTxoRpc spec version it targets.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@nicolasLuduena nicolasLuduena marked this pull request as ready for review January 5, 2026 19:36
Copy link
Copy Markdown

@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: 7

Fix all issues with AI Agents 🤖
In @src/serve/grpc/submit.rs:
- Around line 199-219: eval_tx currently treats request.into_inner().tx as a
single optional tx but the proto uses a Vec of txs and EvalTxResponse.report
expects a repeated list; update eval_tx to iterate over request.into_inner().tx
(the Vec), for each entry extract tx_raw like you do now, call
validate_tx(&self.domain, &chain, &tx_raw) and tx_eval_to_u5c, wrap each result
in an AnyChainEval with Chain::Cardano(result), collect those into a
Vec<AnyChainEval> and assign that collection to the response field (the repeated
report field on EvalTxResponse) instead of building a single
Option<AnyChainEval>.
- Line 92: The TxEval struct's fee field is being set to None but the current
pallas TxEval definition expects a concrete numeric type (not Option), causing a
type mismatch; update the assignment in the code that constructs TxEval (look
for the TxEval instantiation around submit.rs) to provide the correct numeric
value or zero (e.g., 0u64 or the exact integer type used by TxEval) instead of
None, or change the surrounding conversion to map an Option to the concrete type
(e.g., .unwrap_or(0) or a proper compute_fee() result) so the fee field matches
the TxEval fee type.

In @src/serve/grpc/sync.rs:
- Around line 54-61: The struct construction in point_to_blockref is setting a
non-existent timestamp field on u5c::sync::BlockRef; remove the timestamp:
timestamp, assignment from the BlockRef literal in point_to_blockref (or, if the
newer BlockRef in the dependency should include timestamp, update the dependency
and adjust imports accordingly) so the BlockRef is built only with existing
fields (e.g., hash and slot) and any timestamp handling is done separately.
- Around line 43-51: The BlockRef initialization is using fields not in the
UTxO-RPC spec; change the u5c::sync::BlockRef construction in the
mapper.map_block_cbor handling to only set index and hash (remove timestamp,
slot, and height assignments). Populate index from header.height and hash from
header.hash so the struct matches the spec exactly.

In @src/serve/grpc/watch.rs:
- Around line 193-210: The TipEvent::Apply arm uses
u5c::watch::watch_tx_response::Action::Idle which doesn't exist; remove that
variant and, when txs.is_empty(), return an empty vector instead of attempting
to emit Idle (i.e., replace the raw_to_blockref/Idle branch with vec![]), or
alternatively if you need to signal the block choose an existing variant (e.g.,
wrap the block ref into Action::Apply using the same WatchTxResponse shape) but
do not reference Action::Idle; update the TipEvent::Apply logic and calls to
raw_to_blockref accordingly.
🧹 Nitpick comments (3)
src/serve/grpc/watch.rs (2)

173-185: Inconsistency: Missing timestamp field compared to sync.rs version.

The raw_to_blockref helper in sync.rs (lines 38-51) includes a timestamp field in its BlockRef construction, but this version omits it. If u5c::watch::BlockRef supports timestamp (as the sync version does), consider adding it for consistency.

Some(u5c::watch::BlockRef {
    slot: header.slot,
    hash: header.hash,
    height: header.height,
    // timestamp: block.timestamp, // Add if supported by the spec
})

211-218: Consider handling TipEvent::Undo with empty transactions consistently.

The Apply branch now handles the case when block_to_txs returns empty by emitting an Idle response (assuming the build issue is resolved). However, the Undo branch does not have similar handling—if an undo block has no matching transactions, it will emit an empty stream.

Consider whether this asymmetry is intentional or if Undo should also emit an idle/status response when no transactions match the predicate.

src/serve/grpc/sync.rs (1)

233-235: Timestamp lookup with silent fallback to 0.

The get_slot_timestamp call uses unwrap_or(0) as a fallback. While this prevents panics, returning timestamp 0 (Unix epoch) for missing slots could be misleading to clients. Consider:

  1. Logging when the fallback is used, or
  2. Returning an error if timestamp lookup fails for a known slot

This becomes moot if timestamp isn't a valid field yet, but worth noting for when it is.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb64e93 and 66b7709.

📒 Files selected for processing (4)
  • src/serve/grpc/query.rs
  • src/serve/grpc/submit.rs
  • src/serve/grpc/sync.rs
  • src/serve/grpc/watch.rs
🧰 Additional context used
🧬 Code graph analysis (3)
src/serve/grpc/watch.rs (1)
src/serve/grpc/sync.rs (1)
  • raw_to_blockref (39-52)
src/serve/grpc/submit.rs (2)
crates/core/src/facade.rs (7)
  • receive_tx (150-162)
  • chain (22-22)
  • chain (60-60)
  • chain (64-64)
  • chain (67-67)
  • chain (70-70)
  • validate_tx (136-148)
crates/cardano/src/validate.rs (1)
  • validate_tx (12-92)
src/serve/grpc/sync.rs (3)
crates/core/src/lib.rs (1)
  • point (386-390)
crates/core/src/batch.rs (1)
  • point (70-75)
src/serve/grpc/watch.rs (1)
  • raw_to_blockref (173-185)
🪛 GitHub Actions: CI
src/serve/grpc/watch.rs

[error] 199-199: no variant or associated item named Idle found for enum pallas::interop::pallas_utxorpc::spec::watch::watch_tx_response::Action in the current scope

🪛 GitHub Check: Check Build
src/serve/grpc/query.rs

[failure] 219-219:
struct AnyUtxoData has no field named block_ref

src/serve/grpc/watch.rs

[failure] 199-199:
no variant or associated item named Idle found for enum pallas::interop::pallas_utxorpc::spec::watch::watch_tx_response::Action in the current scope

src/serve/grpc/submit.rs

[failure] 92-92:
mismatched types


[failure] 138-138:
the trait bound Vec<bytes::Bytes>: From<Vec<u8>> is not satisfied


[failure] 128-128:
no method named ok_or_else found for struct Vec<pallas::interop::pallas_utxorpc::spec::submit::AnyChainTx> in the current scope


[failure] 219-219:
mismatched types


[failure] 202-202:
no method named ok_or_else found for struct Vec<pallas::interop::pallas_utxorpc::spec::submit::AnyChainTx> in the current scope

src/serve/grpc/sync.rs

[failure] 58-58:
struct pallas::interop::pallas_utxorpc::spec::sync::BlockRef has no field named timestamp


[failure] 50-50:
struct pallas::interop::pallas_utxorpc::spec::sync::BlockRef has no field named timestamp


[failure] 74-74:
struct FollowTipResponse has no field named tip

🔇 Additional comments (1)
src/serve/grpc/query.rs (1)

212-220: No action needed. The block_ref field is already present and functional in u5c::query::AnyUtxoData as used in pallas 1.0.0-alpha.3. The code at line 219 correctly sets block_ref: None, and the field is also successfully used elsewhere in the codebase (line 399 with block_ref: Some(...)). This review comment was based on incorrect static analysis.

Likely an incorrect or invalid review comment.

Comment thread src/serve/grpc/submit.rs
})
.collect(),
fee: 0, // TODO
fee: None, // TODO
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Build failure: Type mismatch for fee field.

Static analysis indicates a type mismatch on the fee field. The field likely expects a concrete numeric type rather than Option<_>. Verify the expected type in the current pallas spec version.

#!/bin/bash
# Search for TxEval struct definition to see expected fee type
ast-grep --pattern 'struct TxEval {
  $$$
}'

rg -n "fee:" --type rust -C 3 | grep -i "txeval" -A 5 -B 5 | head -40
🧰 Tools
🪛 GitHub Check: Check Build

[failure] 92-92:
mismatched types

🤖 Prompt for AI Agents
In @src/serve/grpc/submit.rs around line 92, The TxEval struct's fee field is
being set to None but the current pallas TxEval definition expects a concrete
numeric type (not Option), causing a type mismatch; update the assignment in the
code that constructs TxEval (look for the TxEval instantiation around submit.rs)
to provide the correct numeric value or zero (e.g., 0u64 or the exact integer
type used by TxEval) instead of None, or change the surrounding conversion to
map an Option to the concrete type (e.g., .unwrap_or(0) or a proper
compute_fee() result) so the fee field matches the TxEval fee type.

Comment thread src/serve/grpc/submit.rs
Comment on lines +126 to +139
let tx = message
.tx
.ok_or_else(|| Status::invalid_argument("missing tx"))?;
let tx_bytes = match tx.r#type {
Some(any_chain_tx::Type::Raw(bytes)) => bytes,
_ => return Err(Status::invalid_argument("missing or unsupported tx type")),
};

Ok(Response::new(SubmitTxResponse { r#ref: hashes }))
let hash = receive_tx(&self.domain, &chain, tx_bytes.as_ref())
.map_err(|e| Status::invalid_argument(format!("could not process tx: {e}")))?;

Ok(Response::new(SubmitTxResponse {
r#ref: hash.to_vec().into(),
}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Build failure: API mismatch—message.tx is a Vec, not Option.

Static analysis shows that ok_or_else is not found for Vec<AnyChainTx>. This indicates that SubmitTxRequest.tx is still a Vec (multi-transaction) in the current pallas version, not a single optional field.

The code assumes a spec change to single-transaction requests that hasn't been reflected in the dependency yet. Either:

  1. Update the pallas dependency to one that matches the 0.18.1 spec with single-tx requests, or
  2. Revert to handling message.tx as a Vec until the dependency is updated.
🔎 If reverting to Vec-based handling is needed
-        let tx = message
-            .tx
-            .ok_or_else(|| Status::invalid_argument("missing tx"))?;
-        let tx_bytes = match tx.r#type {
-            Some(any_chain_tx::Type::Raw(bytes)) => bytes,
-            _ => return Err(Status::invalid_argument("missing or unsupported tx type")),
-        };
-
-        let hash = receive_tx(&self.domain, &chain, tx_bytes.as_ref())
-            .map_err(|e| Status::invalid_argument(format!("could not process tx: {e}")))?;
-
-        Ok(Response::new(SubmitTxResponse {
-            r#ref: hash.to_vec().into(),
-        }))
+        let tx = message.tx.first()
+            .ok_or_else(|| Status::invalid_argument("missing tx"))?;
+        let tx_bytes = match &tx.r#type {
+            Some(any_chain_tx::Type::Raw(bytes)) => bytes.clone(),
+            _ => return Err(Status::invalid_argument("missing or unsupported tx type")),
+        };
+
+        let hash = receive_tx(&self.domain, &chain, tx_bytes.as_ref())
+            .map_err(|e| Status::invalid_argument(format!("could not process tx: {e}")))?;
+
+        Ok(Response::new(SubmitTxResponse {
+            r#ref: vec![hash.to_vec().into()],
+        }))

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

🧰 Tools
🪛 GitHub Check: Check Build

[failure] 138-138:
the trait bound Vec<bytes::Bytes>: From<Vec<u8>> is not satisfied


[failure] 128-128:
no method named ok_or_else found for struct Vec<pallas::interop::pallas_utxorpc::spec::submit::AnyChainTx> in the current scope

Comment thread src/serve/grpc/submit.rs
Comment on lines +199 to +219
let tx = request
.into_inner()
.tx
.into_iter()
.map(|tx| {
tx.r#type
.map(|tx_type| match tx_type {
any_chain_tx::Type::Raw(bytes) => bytes.to_vec(),
})
.unwrap_or_default()
})
.collect();
.ok_or_else(|| Status::invalid_argument("missing tx"))?;

let tx_raw = match tx.r#type {
Some(any_chain_tx::Type::Raw(bytes)) => bytes.to_vec(),
_ => return Err(Status::invalid_argument("missing or unsupported tx type")),
};

let chain = self.domain.read_chain().await;

let handle_eval = |tx_cbor: &[u8]| {
let result = validate_tx(&self.domain, &chain, tx_cbor);
let result = tx_eval_to_u5c(result);
let result = validate_tx(&self.domain, &chain, &tx_raw);
let result = tx_eval_to_u5c(result);

AnyChainEval {
chain: Some(Chain::Cardano(result)),
}
let report = AnyChainEval {
chain: Some(Chain::Cardano(result)),
};

let eval_results: Vec<_> = txs_raw.iter().map(|x| handle_eval(x)).collect();

Ok(Response::new(EvalTxResponse {
report: eval_results,
report: Some(report),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Build failure: Same API mismatch in eval_tx.

The eval_tx method has the same issue—request.tx is a Vec, and EvalTxResponse.report expects a different structure than a single Option<AnyChainEval>.

This needs to be aligned with the actual pallas spec version being used.

🧰 Tools
🪛 GitHub Check: Check Build

[failure] 219-219:
mismatched types


[failure] 202-202:
no method named ok_or_else found for struct Vec<pallas::interop::pallas_utxorpc::spec::submit::AnyChainTx> in the current scope

🤖 Prompt for AI Agents
In @src/serve/grpc/submit.rs around lines 199-219, eval_tx currently treats
request.into_inner().tx as a single optional tx but the proto uses a Vec of txs
and EvalTxResponse.report expects a repeated list; update eval_tx to iterate
over request.into_inner().tx (the Vec), for each entry extract tx_raw like you
do now, call validate_tx(&self.domain, &chain, &tx_raw) and tx_eval_to_u5c, wrap
each result in an AnyChainEval with Chain::Cardano(result), collect those into a
Vec<AnyChainEval> and assign that collection to the response field (the repeated
report field on EvalTxResponse) instead of building a single
Option<AnyChainEval>.

Comment thread src/serve/grpc/sync.rs
Comment on lines +43 to 51
let block = mapper.map_block_cbor(body);
let header = block.header?;

header.map(|h| u5c::sync::BlockRef {
slot: h.slot,
hash: h.hash,
height: h.height,
Some(u5c::sync::BlockRef {
slot: header.slot,
hash: header.hash,
height: header.height,
timestamp: block.timestamp,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the actual code at lines 43-51
cat -n src/serve/grpc/sync.rs | sed -n '40,55p'

Repository: txpipe/dolos

Length of output: 585


🏁 Script executed:

# Search for BlockRef definition to verify field names
rg -n "struct BlockRef" --type rust -A 20 | head -80

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Also check for FollowTipResponse definition
rg -n "struct FollowTipResponse" --type rust -A 15 | head -60

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Check what version of pallas is being used
cat Cargo.toml | grep -A 5 -B 5 "pallas\|u5c"

Repository: txpipe/dolos

Length of output: 780


🏁 Script executed:

# Search more broadly for BlockRef in the entire codebase
rg -n "BlockRef" --type rust | head -30

Repository: txpipe/dolos

Length of output: 635


🏁 Script executed:

# Check if u5c is imported and where it comes from
rg -n "use.*u5c" --type rust | head -20

Repository: txpipe/dolos

Length of output: 436


🏁 Script executed:

# Look for u5c in dependencies
rg -n "u5c" Cargo.toml

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Check if this project uses cargo and try to see dependency tree
cd . && if [ -f Cargo.lock ]; then grep -A 5 "name = \"u5c\"" Cargo.lock 2>/dev/null || echo "u5c not in lock file"; fi

Repository: txpipe/dolos

Length of output: 76


🏁 Script executed:

# Find where BlockRef is defined - check pallas interop module
find . -name "*.rs" -type f | xargs rg -l "sync::BlockRef" | head -10

Repository: txpipe/dolos

Length of output: 80


🏁 Script executed:

# Check if there's a generated or proto file for BlockRef
find . -name "*.rs" -o -name "*.proto" | xargs rg -l "BlockRef" 2>/dev/null | head -15

Repository: txpipe/dolos

Length of output: 106


🏁 Script executed:

# Look at the watch.rs file to see how BlockRef is used there
cat -n src/serve/grpc/watch.rs | sed -n '170,190p'

Repository: txpipe/dolos

Length of output: 691


🏁 Script executed:

# Check if there's documentation or comments about BlockRef fields
rg -B 5 -A 10 "struct BlockRef|BlockRef {" --type rust | head -100

Repository: txpipe/dolos

Length of output: 171


🏁 Script executed:

# Search for any compilation or build test output
if [ -f target/debug/dolos ] || [ -f target/release/dolos ]; then 
  echo "Binary exists"; 
else 
  echo "No compiled binary found"; 
fi

Repository: txpipe/dolos

Length of output: 80


🏁 Script executed:

# Look at complete sync.rs file structure to understand the functions
cat -n src/serve/grpc/sync.rs | head -100

Repository: txpipe/dolos

Length of output: 3713


🏁 Script executed:

# Check the exact point_to_blockref function
cat -n src/serve/grpc/sync.rs | sed -n '54,65p'

Repository: txpipe/dolos

Length of output: 486


🏁 Script executed:

# Search for any TODO or FIXME comments near these lines
rg -n "TODO|FIXME|XXX" src/serve/grpc/sync.rs

Repository: txpipe/dolos

Length of output: 222


🏁 Script executed:

# Try to find pallas version details and BlockRef definition
web_search pallas utxorpc BlockRef fields timestamp

Repository: txpipe/dolos

Length of output: 106


🌐 Web query:

pallas 1.0.0-alpha.3 BlockRef timestamp field utxorpc

💡 Result:

Short answer: The UTxO‑RPC spec's BlockRef does NOT include a timestamp field — it only defines index and hash — so pallas' utxorpc types follow the spec (no BlockRef.timestamp). See the UTxO‑RPC Sync/Watch spec and the pallas repo/release. [1][2]

Sources

  • UTxO‑RPC Sync/Watch module — BlockRef fields (index, hash). [1]
  • txpipe/pallas release / pallas-utxorpc crate (pallas implements UTxO‑RPC interop). [2]

🌐 Web query:

pallas interop utxorpc sync BlockRef struct definition

💡 Result:

BlockRef (UTxO RPC sync module) — fields:

  • index: uint64 (height/slot)
  • hash: bytes

Rust (pallas interop/utxorpc) equivalent (approx):
pub struct BlockRef {
pub index: u64,
pub hash: Bytes, // raw block hash bytes
}

Sources: UTxO RPC sync spec and pallas docs. [1][2]

References
[1] UTxO RPC — Sync module (BlockRef: index uint64, hash bytes).
[2] pallas crate — interop::utxorpc::spec::sync exposes BlockRef.


Remove timestamp, slot, and height fields from BlockRef assignments.

The UTxO-RPC spec defines BlockRef with only index and hash fields. Pallas implements the spec exactly, so assigning timestamp (line 50), slot, and height fields will fail compilation. Update the struct initialization to only include the fields defined in the spec.

🧰 Tools
🪛 GitHub Check: Check Build

[failure] 50-50:
struct pallas::interop::pallas_utxorpc::spec::sync::BlockRef has no field named timestamp

🤖 Prompt for AI Agents
In @src/serve/grpc/sync.rs around lines 43-51, The BlockRef initialization is
using fields not in the UTxO-RPC spec; change the u5c::sync::BlockRef
construction in the mapper.map_block_cbor handling to only set index and hash
(remove timestamp, slot, and height assignments). Populate index from
header.height and hash from header.hash so the struct matches the spec exactly.

Comment thread src/serve/grpc/sync.rs
Comment on lines +54 to 61
fn point_to_blockref(point: &ChainPoint, timestamp: u64) -> u5c::sync::BlockRef {
BlockRef {
hash: point.hash().map(|h| h.to_vec()).unwrap_or_default().into(),
slot: point.slot(),
timestamp,
..Default::default()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Build failure: timestamp field assignment will fail.

The point_to_blockref function attempts to set a timestamp field that doesn't exist on BlockRef. This needs to be removed or the dependency updated.

🧰 Tools
🪛 GitHub Check: Check Build

[failure] 58-58:
struct pallas::interop::pallas_utxorpc::spec::sync::BlockRef has no field named timestamp

🤖 Prompt for AI Agents
In @src/serve/grpc/sync.rs around lines 54-61, The struct construction in
point_to_blockref is setting a non-existent timestamp field on
u5c::sync::BlockRef; remove the timestamp: timestamp, assignment from the
BlockRef literal in point_to_blockref (or, if the newer BlockRef in the
dependency should include timestamp, update the dependency and adjust imports
accordingly) so the BlockRef is built only with existing fields (e.g., hash and
slot) and any timestamp handling is done separately.

Comment thread src/serve/grpc/sync.rs
Comment on lines +67 to 89
match event {
TipEvent::Apply(_, block) => {
let block_ref = raw_to_blockref(mapper, block);
u5c::sync::FollowTipResponse {
action: Some(u5c::sync::follow_tip_response::Action::Apply(
raw_to_anychain(mapper, block),
)),
tip: block_ref,
}
}
TipEvent::Undo(_, block) => u5c::sync::FollowTipResponse {
action: Some(u5c::sync::follow_tip_response::Action::Undo(
raw_to_anychain(mapper, block),
)),
tip: None, // TODO: we don't have easy access to the new tip here
},
TipEvent::Mark(x) => u5c::sync::FollowTipResponse {
action: Some(u5c::sync::follow_tip_response::Action::Reset(
point_to_blockref(x, 0), // TODO: we don't have the timestamp here
)),
tip: Some(point_to_blockref(x, 0)),
},
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Build failure: FollowTipResponse has no tip field.

Static analysis indicates that FollowTipResponse doesn't have a tip field in the current pallas version. The restructured response construction will fail to compile.

Additionally, note the TODO comments at lines 81 and 85 indicating incomplete timestamp handling:

  • Line 81: tip: None, // TODO: we don't have easy access to the new tip here
  • Line 85: point_to_blockref(x, 0), // TODO: we don't have the timestamp here

These should be tracked for follow-up once the dependency is updated.

Would you like me to open an issue to track the TODO items for proper timestamp handling in Undo and Mark events?

🧰 Tools
🪛 GitHub Check: Check Build

[failure] 74-74:
struct FollowTipResponse has no field named tip

Comment thread src/serve/grpc/watch.rs
Comment on lines +193 to +210
TipEvent::Apply(_, block) => {
let txs = block_to_txs(block, mapper, request);
if txs.is_empty() {
let block_ref = raw_to_blockref(mapper, block);
if let Some(r) = block_ref {
vec![u5c::watch::WatchTxResponse {
action: Some(u5c::watch::watch_tx_response::Action::Idle(r)),
}]
} else {
vec![]
}
} else {
txs.into_iter()
.map(u5c::watch::watch_tx_response::Action::Apply)
.map(|x| u5c::watch::WatchTxResponse { action: Some(x) })
.collect()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file in question
cat -n src/serve/grpc/watch.rs | sed -n '190,215p'

Repository: txpipe/dolos

Length of output: 1353


🏁 Script executed:

# Check pallas version in Cargo.toml
rg -A2 -B2 "pallas" Cargo.toml

Repository: txpipe/dolos

Length of output: 485


🏁 Script executed:

# Search for Action enum definition and variants in the codebase
rg -n "enum Action" --type rust -A 10 | head -40

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Search for where Action is used with Idle
rg -n "Action::Idle" --type rust

Repository: txpipe/dolos

Length of output: 169


🏁 Script executed:

# Check if there's a proto file that defines the watch service
fd -e proto | head -20

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Search for watch module or Action definition in the codebase
find . -type f -name "*.rs" | xargs grep -l "watch_tx_response" | head -10

Repository: txpipe/dolos

Length of output: 81


🏁 Script executed:

# Look for the u5c module definition
fd -e rs | xargs grep -l "u5c::" | head -10

Repository: txpipe/dolos

Length of output: 173


🏁 Script executed:

# Check if u5c is a generated proto module
find . -type f -name "*.rs" | xargs grep -l "mod u5c\|mod watch" | head -10

Repository: txpipe/dolos

Length of output: 79


🏁 Script executed:

# Search for Action enum definition more broadly
rg -n "enum.*Action" --type rust -B2 -A10 | head -60

Repository: txpipe/dolos

Length of output: 821


🏁 Script executed:

# Check the grpc/mod.rs file to see how u5c is defined
cat -n src/serve/grpc/mod.rs

Repository: txpipe/dolos

Length of output: 4090


🏁 Script executed:

# Search for where u5c module is generated or imported
rg -n "u5c" src/serve/grpc/mod.rs -B3 -A3

Repository: txpipe/dolos

Length of output: 1826


🏁 Script executed:

# Look for the proto or u5c module definition
find . -type f \( -name "*.proto" -o -name "*.rs" \) | xargs grep -l "watch_tx_response\|Action" | grep -v target

Repository: txpipe/dolos

Length of output: 319


🏁 Script executed:

# Search for all uses of Action enum in the watch.rs file
rg -n "Action::" src/serve/grpc/watch.rs

Repository: txpipe/dolos

Length of output: 286


🏁 Script executed:

# Check if there's any documentation or comments about Action variants
rg -B5 -A5 "Action::Idle|Action::Apply|Action::Undo" src/serve/grpc/watch.rs

Repository: txpipe/dolos

Length of output: 1105


🏁 Script executed:

# Search in pallas related documentation or comments about what variants exist
rg -n "Idle|Apply|Undo" --type rust | grep -i "action\|watch" | head -20

Repository: txpipe/dolos

Length of output: 725


🏁 Script executed:

# Check if there are any build errors or TODO comments related to this
rg -n "TODO|FIXME|XXX|HACK" src/serve/grpc/watch.rs | head -20

Repository: txpipe/dolos

Length of output: 165


🏁 Script executed:

# Look at the git history to see if Action::Idle was recently added
git log --oneline -20 -- src/serve/grpc/watch.rs

Repository: txpipe/dolos

Length of output: 93


🏁 Script executed:

# Check if there's any configuration or feature flags that might affect this
cat Cargo.toml | grep -A20 "\[dependencies\]"

Repository: txpipe/dolos

Length of output: 582


🏁 Script executed:

# Try to find any proto files or schema that would define the Action enum
find . -name "*.proto" -type f

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Search for pallas utxorpc documentation or comments
rg -n "utxorpc" --type rust -B3 -A3 | head -40

Repository: txpipe/dolos

Length of output: 2494


🏁 Script executed:

# Check if there are any recent issues or comments about Action variants
rg -n "Idle\|Apply\|Undo" --type rust -B2 -A2 | grep -i "action\|watch_tx" | head -30

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Look for any test files or examples that might indicate which variants are valid
find . -name "*.rs" -type f | xargs grep -l "watch_tx_response" | xargs cat | head -100

Repository: txpipe/dolos

Length of output: 3502


🏁 Script executed:

# Check the sync.rs file to see how it handles Action variants
cat -n src/serve/grpc/sync.rs | sed -n '65,85p'

Repository: txpipe/dolos

Length of output: 1130


🏁 Script executed:

# Look for any build errors or compilation output
cat Cargo.toml | head -20

Repository: txpipe/dolos

Length of output: 533


🏁 Script executed:

# Check if there's a .cargo or build directory with any cached build info
ls -la | grep -E "^d" | head -20

Repository: txpipe/dolos

Length of output: 687


🏁 Script executed:

# Search for the pallas crate reference to understand what Action variants should exist
rg -n "watch_tx_response" --type rust -B5 -A5

Repository: txpipe/dolos

Length of output: 1805


🏁 Script executed:

# Check if there's documentation about the watch_tx_response Action enum
rg -n "enum.*Action|type.*Action" --type rust -A15 | grep -A15 "watch"

Repository: txpipe/dolos

Length of output: 38


🏁 Script executed:

# Let's check what pallas version 1.0.0-alpha.3 contains by looking at dependencies
rg -n "version.*1.0.0-alpha.3" Cargo.toml

Repository: txpipe/dolos

Length of output: 132


🌐 Web query:

pallas 1.0.0-alpha.3 watch_tx_response Action enum variants utxorpc

💡 Result:

The Action enum for watch_tx_response has two variants: Apply and Undo. [1][2]

Sources:

  • pallas crate index (interop::utxorpc spec listing). [1]
  • UTxO RPC Watch spec (WatchTxResponse: apply / undo). [2]

Remove or replace Action::Idle variant—it does not exist in pallas 1.0.0-alpha.3.

The code at line 199 attempts to use u5c::watch::watch_tx_response::Action::Idle, but the Action enum in the current pallas version only supports Apply and Undo variants. This will cause a compilation failure.

Either adjust the logic to use an existing variant or upgrade pallas to a version that includes the Idle variant.

🧰 Tools
🪛 GitHub Actions: CI

[error] 199-199: no variant or associated item named Idle found for enum pallas::interop::pallas_utxorpc::spec::watch::watch_tx_response::Action in the current scope

🪛 GitHub Check: Check Build

[failure] 199-199:
no variant or associated item named Idle found for enum pallas::interop::pallas_utxorpc::spec::watch::watch_tx_response::Action in the current scope

🤖 Prompt for AI Agents
In @src/serve/grpc/watch.rs around lines 193-210, The TipEvent::Apply arm uses
u5c::watch::watch_tx_response::Action::Idle which doesn't exist; remove that
variant and, when txs.is_empty(), return an empty vector instead of attempting
to emit Idle (i.e., replace the raw_to_blockref/Idle branch with vec![]), or
alternatively if you need to signal the block choose an existing variant (e.g.,
wrap the block ref into Action::Apply using the same WatchTxResponse shape) but
do not reference Action::Idle; update the TipEvent::Apply logic and calls to
raw_to_blockref accordingly.

Copy link
Copy Markdown

@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: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f63e6b5 and 29aced8.

📒 Files selected for processing (1)
  • src/serve/grpc/sync.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/serve/grpc/sync.rs (1)
src/serve/grpc/watch.rs (1)
  • raw_to_blockref (173-185)
⏰ 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: Check Build
🔇 Additional comments (1)
src/serve/grpc/sync.rs (1)

54-61: Compilation failure: Invalid fields in BlockRef construction.

This issue was already flagged in previous reviews. The sync::BlockRef struct does not have slot or timestamp fields; only index and hash are defined in the UTxO-RPC spec.

Likely an incorrect or invalid review comment.

Comment thread src/serve/grpc/sync.rs
Comment on lines +136 to +165
let lookup = |br: &u5c::sync::BlockRef| -> Result<BlockBody, Status> {
if !br.hash.is_empty() {
if let Some(body) = store
.get_block_by_hash(&br.hash)
.map_err(|_| Status::internal("Failed to query chain service."))?
{
return Ok(body);
}
}

if br.height != 0 {
if let Some(body) = store
.get_block_by_number(&br.height)
.map_err(|_| Status::internal("Failed to query chain service."))?
{
return Ok(body);
}
}

if br.slot != 0 {
if let Some(body) = store
.get_block_by_slot(&br.slot)
.map_err(|_| Status::internal("Failed to query chain service."))?
{
return Ok(body);
}
}

Err(Status::not_found(format!("Failed to find block: {br:?}")))
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Compilation failure: Invalid field access on BlockRef.

The lookup closure attempts to access br.height (line 146) and br.slot (line 155), but sync::BlockRef only has index and hash fields per the UTxO-RPC spec. This will cause compilation to fail.

Clarification needed

The spec defines index as a uint64, but it's unclear whether this represents slot or height in the Cardano context. You'll need to:

  1. Determine what index represents (slot, height, or block number)
  2. Rewrite the lookup logic to use only hash and index
  3. Decide on an appropriate fallback strategy if index doesn't cover all lookup scenarios

@scarmuega scarmuega merged commit 2b6de0b into txpipe:main Jan 16, 2026
8 of 9 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.

3 participants