Skip to content

feat: Implement u5c submit interface#76

Merged
scarmuega merged 3 commits intotxpipe:mainfrom
gonzalezzfelipe:feat/u5c-submit-interface
Feb 10, 2026
Merged

feat: Implement u5c submit interface#76
scarmuega merged 3 commits intotxpipe:mainfrom
gonzalezzfelipe:feat/u5c-submit-interface

Conversation

@gonzalezzfelipe
Copy link
Contributor

@gonzalezzfelipe gonzalezzfelipe commented Jul 2, 2025

Summary by CodeRabbit

  • New Features

    • Transaction submission support (Cardano backend) and new submit-tx request handler
    • Configurable submit backend with runtime wiring
    • Submission metrics counter for monitoring
  • Improvements

    • Structured submit errors with clearer internal/invalid distinctions
    • Better logging for related handlers and improved error mappings

@SupernaviX
Copy link
Collaborator

Note that this interface isn't useful right now, because it only works if you pass in a complete TX and the txbuilder only builds partial TXs. I don't think tx3 will change that, based on santi's sample code

@gonzalezzfelipe
Copy link
Contributor Author

gonzalezzfelipe commented Jul 2, 2025

Note that this interface isn't useful right now, because it only works if you pass in a complete TX and the txbuilder only builds partial TXs. I don't think tx3 will change that, based on santi's sample code

IIUC, the submit module is precisely for passing a complete tx. Tx building would be left to the client. Particularly, I added a function to pass a complete tx through the jsonrpc and submiting it that way. One could also send a Tx for it to be signed by a worker signer.

@SupernaviX
Copy link
Collaborator

Right, this interface is for passing a complete TX. I'm saying that because of the APIs balius exposes to the worker, it'll only have a complete TX to submit if that TX was passed into the worker from outside.

So this interface lets people write an endpoint like the one you wrote, which takes a transaction and submits it. But there's no way for the worker to build and submit transactions, which is why I'm saying it isn't useful. If the caller has a signed transaction and a demeter key, they probably have access to a "submit transaction" API already, and balius isn't buying them much.

@gonzalezzfelipe
Copy link
Contributor Author

Right, this interface is for passing a complete TX. I'm saying that because of the APIs balius exposes to the worker, it'll only have a complete TX to submit if that TX was passed into the worker from outside.

So this interface lets people write an endpoint like the one you wrote, which takes a transaction and submits it. But there's no way for the worker to build and submit transactions, which is why I'm saying it isn't useful. If the caller has a signed transaction and a demeter key, they probably have access to a "submit transaction" API already, and balius isn't buying them much.

Why is there no way for the worker to build the transaction? I understand a tx builder would make it easier, but its doable now. Even so, this is the shape that the interface will take, with or without tx builder. Should we not implement it till we have a way of building txs?

@SupernaviX
Copy link
Collaborator

Why is there no way for the worker to build the transaction? I understand a tx builder would make it easier, but its doable now.

It's technically possible to build a TX without a TX builder, but it's hard enough to do that it's practically impossible; you'd end up writing your own TX builder inside of the worker, reimplementing balancing and fee estimation and everything else yourself.

Even so, this is the shape that the interface will take, with or without tx builder. Should we not implement it till we have a way of building txs?

Yeah, that's all I'm saying. I don't think there's a point to implementing the interface if there isn't a way to use it.

@Benja272
Copy link

Hi, i think this PR makes more sense right now considering that the idea is to include tx3 building (my understanding) . I am exploring the capabilities of balius and i think this is definitely useful.

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Adds a SubmitHost wrapper and U5C submit client, wires submit interface into worker/runtime construction, adds submit_tx metrics and new submit-error WIT variant, and propagates submit error handling into SDK and daemon configuration.

Changes

Cohort / File(s) Summary
Submit Runtime Core
balius-runtime/src/lib.rs, balius-runtime/src/metrics.rs, balius-runtime/src/submit/mod.rs
Introduce SubmitHost (holds worker_id, Submit, metrics), add Error::Submit variant, add submit_tx counter and Metrics::submit_tx(). Replace impl wit::Host on raw Submit with SubmitHost that records metrics and delegates to inner submit sources.
U5C Submission Client
balius-runtime/src/submit/u5c.rs
New module implementing a Cardano submit client via utxorpc: Config (endpoint + headers), Submit wrapper, async new() and submit_tx() with mapping of utxorpc errors to wit::SubmitError.
Daemon Config & Startup
baliusd/src/config.rs, baliusd/src/main.rs
Add SubmitConfig enum and Config.submit: Option<SubmitConfig>; provide async into_submit() conversion producing Submit::U5C or Submit::Mock. Integrate .with_submit(...into_submit().await) into daemon startup.
SDK Error Integration
balius-sdk/src/qol.rs
Add Error::Submit(wit::balius::app::submit::SubmitError) and From<wit::...::SubmitError> for Error; map SDK submit errors into wit::HandleError with code 10.
Examples / Handlers
examples/comprehensive/src/lib.rs
Add SubmitTxParams and submit_tx request handler that decodes hex CBOR and calls balius_sdk::wit::...::submit_tx. Register "submit-tx" handler. Minor change to LedgerSearchUtxosParams derivation.
WIT Interface
wit/balius.wit
Change submit-error from u32 to a variant: submit-error { internal(string), invalid(string) }, updating submit-tx error payload shape.
Example log tweak
examples/asteria-tracker/src/lib.rs
Adjusted UTxO handler debug log to use a named placeholder for the operation; no functional change.

Sequence Diagram

sequenceDiagram
    participant Worker as Worker
    participant SubmitHost as SubmitHost
    participant Metrics as Metrics
    participant U5C as U5C Client

    Worker->>SubmitHost: submit_tx(cbor)
    SubmitHost->>Metrics: submit_tx(worker_id)
    Metrics->>Metrics: increment counter
    alt Submit::U5C
        SubmitHost->>U5C: submit_tx(cbor)
        U5C->>U5C: send to utxorpc endpoint
        alt Success
            U5C-->>SubmitHost: Ok(())
        else GrpcError(code=3)
            U5C-->>SubmitHost: Err(Invalid)
        else Other Error
            U5C-->>SubmitHost: Err(Internal)
        end
    else Submit::Mock
        SubmitHost->>SubmitHost: print hex & return Ok
    else Submit::Custom
        SubmitHost->>SubmitHost: lock custom Submitter and delegate
    end
    SubmitHost-->>Worker: Result<(), SubmitError>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop and bind the submit stream,

Counters tick while CBOR gleams,
U5C paths hum, errors named anew,
Workers link, metrics count each view,
A tiny rabbit stamps "HOP" — job through!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% 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 title accurately and specifically describes the main change: implementation of a u5c submit interface. It clearly maps to the primary purpose of the changeset across multiple files.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


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.

Copy link

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

🤖 Fix all issues with AI agents
In `@balius-runtime/src/metrics.rs`:
- Around line 82-85: The submit_tx metric is missing the finalizing .build()
call causing a compile error; update the meter initialization for submit_tx (the
let submit_tx = meter.u64_counter("submit_tx").with_description("Amount of
submit_tx calls per worker.")) to call .build() at the end so it becomes a
completed counter before the next declaration (similar to how
signer_sign_payload is initialized).

In `@examples/comprehensive/src/lib.rs`:
- Around line 167-172: The function submit_tx is missing its final closing brace
causing a syntax error; locate the submit_tx function (signature: fn
submit_tx(_: Config<MyConfig>, request: Params<SubmitTxParams>) ->
WorkerResult<()>) and add the missing `}` immediately after the Ok(()) return so
the function that decodes cbor (hex::decode(...).map_err(|_| Error::BadParams)?)
and calls balius_sdk::wit::balius::app::submit::submit_tx(&cbor)? is properly
closed before the next item (LedgerSearchUtxosParams).
🧹 Nitpick comments (5)
balius-runtime/src/metrics.rs (1)

227-231: Missing blank line between methods.

The submit_tx method follows the same pattern as other metric methods, which is correct. However, there's a missing blank line between submit_tx and signer_sign_payload methods.

✨ Proposed fix for formatting consistency
     pub fn submit_tx(&self, worker_id: &str) {
         self.submit_tx
             .add(1, &[KeyValue::new("worker", worker_id.to_owned())]);
     }
+
     pub fn signer_sign_payload(&self, worker_id: &str) {
baliusd/src/main.rs (1)

14-25: Consider returning Result instead of panicking on initialization failure.

The .expect() call will panic if the u5c client initialization fails. While this occurs at daemon startup, propagating the error would allow for more graceful error handling and consistent error reporting via the existing miette diagnostics infrastructure used elsewhere in this file.

♻️ Proposed refactor
 impl Config {
-    pub async fn into_submit(&self) -> balius_runtime::submit::Submit {
+    pub async fn into_submit(&self) -> miette::Result<balius_runtime::submit::Submit> {
         match &self.submit {
-            Some(SubmitConfig::U5c(cfg)) => balius_runtime::submit::Submit::U5C(
+            Some(SubmitConfig::U5c(cfg)) => Ok(balius_runtime::submit::Submit::U5C(
                 balius_runtime::submit::u5c::Submit::new(cfg)
                     .await
-                    .expect("Failed to convert config into submit interface"),
-            ),
-            None => balius_runtime::submit::Submit::Mock,
+                    .into_diagnostic()
+                    .context("setting up submit interface")?,
+            )),
+            None => Ok(balius_runtime::submit::Submit::Mock),
         }
     }
 }

And at line 113:

-        .with_submit(config.into_submit().await)
+        .with_submit(config.into_submit().await?)
examples/comprehensive/src/lib.rs (1)

161-165: Consider adding #[serde_as] annotation for consistency.

Other param structs in this file use #[serde_as] annotation even when they don't have custom serialization. For consistency, consider adding it here, or alternatively remove it from structs that don't need it.

balius-runtime/src/submit/u5c.rs (1)

36-44: Consider using a named constant for the gRPC status code.

The hardcoded value 3 represents INVALID_ARGUMENT in gRPC status codes. Using a named constant or the tonic/gRPC library's code enum would improve readability and maintainability.

♻️ Proposed refactor
+// gRPC status code for INVALID_ARGUMENT
+const GRPC_INVALID_ARGUMENT: i32 = 3;
+
             .map_err(|err| match err {
                 utxorpc::Error::GrpcError(status) => {
                     let code: i32 = status.code().into();
-                    if code == 3 {
+                    if code == GRPC_INVALID_ARGUMENT {
                         wit::SubmitError::Invalid(status.to_string())
                     } else {
                         wit::SubmitError::Internal(status.to_string())
                     }
                 }
balius-runtime/src/submit/mod.rs (1)

34-46: Consider counting successes separately from attempts.
submit_tx increments the counter before the backend call, so failures are included. If the metric is meant to represent successful submissions, consider moving the increment after a successful call (or add a failure counter / rename to attempts).

🔧 Optional adjustment to count only successes
 async fn submit_tx(&mut self, tx: wit::Cbor) -> Result<(), wit::SubmitError> {
-    self.metrics.submit_tx(&self.worker_id);
-    match &mut self.submit {
+    let result = match &mut self.submit {
         Submit::Mock => {
             println!("{}", hex::encode(tx));
             Ok(())
         }
         Submit::U5C(x) => x.submit_tx(tx).await,
         Submit::Custom(x) => {
             let mut lock = x.lock().await;
             lock.submit_tx(tx).await
         }
-    }
+    };
+    if result.is_ok() {
+        self.metrics.submit_tx(&self.worker_id);
+    }
+    result
 }

Copy link

@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

🤖 Fix all issues with AI agents
In `@balius-runtime/src/lib.rs`:
- Around line 68-69: The Error::Submit enum variant in lib.rs is dead — it is
never constructed or converted from the runtime's internal submit errors (the
submit module returns wit::SubmitError) — so either remove the Submit(String)
variant from the Error enum or add a conversion that maps wit::SubmitError into
Error::Submit; to fix, update the Error definition in lib.rs (the Error enum)
and either delete Submit(String) or implement From<wit::SubmitError> for Error
(or adjust places in the submit module to convert/propagate wit::SubmitError
into Error::Submit) so the variant is actually used.
🧹 Nitpick comments (2)
balius-runtime/src/metrics.rs (1)

229-233: Nit: Missing blank line after submit_tx method.

For consistency with the rest of the file, consider adding a blank line between the submit_tx and signer_sign_payload methods.

🧹 Proposed formatting fix
     pub fn submit_tx(&self, worker_id: &str) {
         self.submit_tx
             .add(1, &[KeyValue::new("worker", worker_id.to_owned())]);
     }
+
     pub fn signer_sign_payload(&self, worker_id: &str) {
balius-runtime/src/submit/u5c.rs (1)

36-47: Consider using a named constant for gRPC status code.

The magic number 3 represents INVALID_ARGUMENT in gRPC. Using a named constant would improve readability and maintainability.

📝 Suggested improvement
+// gRPC status code for INVALID_ARGUMENT
+const GRPC_INVALID_ARGUMENT: i32 = 3;
+
 pub async fn submit_tx(&mut self, tx: wit::Cbor) -> Result<(), wit::SubmitError> {
     self.client
         .submit_tx(vec![tx])
         .await
         .map_err(|err| match err {
             utxorpc::Error::GrpcError(status) => {
                 let code: i32 = status.code().into();
-                if code == 3 {
+                if code == GRPC_INVALID_ARGUMENT {
                     wit::SubmitError::Invalid(status.to_string())
                 } else {
                     wit::SubmitError::Internal(status.to_string())
                 }
             }

Alternatively, if tonic is available, you could use tonic::Code::InvalidArgument directly.

@a-osiecki a-osiecki requested a review from scarmuega February 3, 2026 20:34
@scarmuega scarmuega merged commit 23d08fa into txpipe:main Feb 10, 2026
7 of 8 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.

5 participants